Registration
Read an event's registration rules
Read how an authorized event awards entries as one ruleset: registration mode, lottery settings (window in the event's timezone, fee, spots to draw, tickets, categories, reserved entries, waitlist), the qualifying race and trail work requirements, registration questions, add-ons sold at registration, volunteer roles and custom automations, plus a plain-English summary and the saved record ids. Read it before proposing a change so the proposal is a patch over what is saved. Requires canEditEvent on the event.
Method
GET
Path
/v1/events/{eventId}/registration-rules
Required scope
capabilities:readRequest
curl -X GET "https://api.endurancehq.app/v1/events/{eventId}/registration-rules" \
-H "Authorization: Bearer $EHQ_API_KEY"Path parameters
eventIdstring, requiredResponse
Success status: 200
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"event": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"slug": {
"type": "string"
},
"title": {
"type": "string"
},
"timezone": {
"type": [
"string",
"null"
]
},
"date": {
"description": "Event date, YYYY-MM-DD.",
"type": [
"string",
"null"
]
},
"capacity": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"raceOptions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
},
"description": "Distances, for raceOptionIds in the ruleset."
}
},
"required": [
"id",
"slug",
"title",
"timezone",
"date",
"capacity",
"raceOptions"
],
"additionalProperties": false
},
"ruleset": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"fcfs",
"lottery"
],
"description": "How entries are awarded: \"fcfs\" is first come, first served; \"lottery\" fills the field from a weighted drawing of applications."
},
"lottery": {
"description": "Lottery settings. Only the fields present change; null is not allowed while the mode is lottery.",
"anyOf": [
{
"type": "object",
"properties": {
"opensAt": {
"description": "When applications open, local date-time (2026-12-01T06:00).",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d$",
"description": "A local date and time in the event's timezone, \"YYYY-MM-DDTHH:mm\" (2026-12-01T06:00 is 6:00 AM on December 1). Never an ISO instant with Z or an offset; EHQ converts it."
},
{
"type": "null"
}
]
},
"closesAt": {
"description": "When applications close, local date-time (2026-12-12T23:59).",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d$",
"description": "A local date and time in the event's timezone, \"YYYY-MM-DDTHH:mm\" (2026-12-01T06:00 is 6:00 AM on December 1). Never an ISO instant with Z or an offset; EHQ converts it."
},
{
"type": "null"
}
]
},
"drawingDate": {
"description": "The published drawing date (display only; the organizer runs the drawing).",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
{
"type": "null"
}
]
},
"announceAt": {
"description": "Hold results and notices until this local date-time. Null: results go out when the drawing runs.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d$",
"description": "A local date and time in the event's timezone, \"YYYY-MM-DDTHH:mm\" (2026-12-01T06:00 is 6:00 AM on December 1). Never an ISO instant with Z or an offset; EHQ converts it."
},
{
"type": "null"
}
]
},
"applicationFeeCents": {
"description": "Fee to apply, in cents, charged at application. 0 for a free application.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"capacity": {
"description": "Total field size including reserved entries (saved on the event as its capacity): 200.",
"anyOf": [
{
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"spotsToDraw": {
"description": "Spots the drawing fills (180 when 20 are director picks). Null: capacity minus reserved entries. Never more than capacity minus reserved entries.",
"anyOf": [
{
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"acceptanceDeadlineHours": {
"description": "Hours a drawn runner has to accept and pay (default 72).",
"type": "number",
"minimum": 1
},
"tickets": {
"description": "How many tickets each application carries.",
"type": "object",
"properties": {
"base": {
"description": "Tickets every application starts with (default 1).",
"type": "integer",
"minimum": 1,
"maximum": 100
},
"perPriorFinish": {
"description": "Extra tickets per prior finish of this race (1 means one more ticket for each finish). 0 or null: finishes earn nothing.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 100
},
{
"type": "null"
}
]
},
"priorFinishMax": {
"description": "Cap on tickets from prior finishes. Null: no cap.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"countStartsAsFinishes": {
"description": "Count a DNF or DQ start the same as a finish for the per-finish ticket.",
"type": "boolean"
},
"volunteerYears": {
"description": "Tickets for years of volunteering at this race. Null: volunteering earns no tickets.",
"anyOf": [
{
"type": "object",
"properties": {
"afterYears": {
"type": "integer",
"minimum": 0,
"maximum": 50,
"description": "Years of volunteering for this race required before any volunteer-year ticket is earned (2 means a volunteer with 2+ years earns tickets)."
},
"perYear": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Extra tickets per qualifying volunteer year."
},
"max": {
"description": "Cap on tickets from volunteer years. Null: no cap.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"countYearsBeyondMinimum": {
"description": "True: only the years past the minimum earn tickets. False (default): every year earns once the minimum is met.",
"type": "boolean"
}
},
"required": [
"afterYears",
"perYear"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"repeatApplications": {
"description": "Tickets that build up over unsuccessful applications in earlier years.",
"type": "object",
"properties": {
"model": {
"type": "string",
"enum": [
"none",
"linear",
"doubling"
],
"description": "\"none\": no extra tickets for unsuccessful past applications. \"linear\": adds `increment` tickets per unsuccessful year. \"doubling\": doubles each unsuccessful year."
},
"increment": {
"description": "Linear model only: tickets added per unsuccessful year (default 1).",
"type": "number",
"minimum": 0
},
"cap": {
"description": "Hard cap on total tickets after every rule. Null: no cap.",
"anyOf": [
{
"type": "number",
"minimum": 1
},
{
"type": "null"
}
]
},
"resetOnDecline": {
"description": "A runner who is drawn and declines starts over (default true).",
"type": "boolean"
},
"streakBreaksOnSkippedYear": {
"description": "Skipping a year resets the streak (default false).",
"type": "boolean"
}
},
"required": [
"model"
],
"additionalProperties": false
},
"volunteerBonus": {
"description": "Bonus tickets from volunteer credits or hours (separate from volunteer-year tickets).",
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether volunteer credits or hours earn bonus tickets."
},
"source": {
"description": "What earns the bonus: recorded credits, verified hours, or both.",
"type": "string",
"enum": [
"credits",
"hours",
"credits_and_hours"
]
},
"scope": {
"description": "Whose volunteering counts: \"series\" is this race's editions (default), \"event\" this edition only, \"organization\" any of this organizer's events, \"all\" every event on the platform.",
"type": "string",
"enum": [
"all",
"organization",
"series",
"event"
]
},
"creditsPerTicket": {
"description": "Credits required per bonus ticket (default 1).",
"type": "number",
"minimum": 1
},
"hoursPerTicket": {
"description": "Verified hours required per bonus ticket (default 4).",
"type": "number",
"minimum": 1
},
"max": {
"description": "Most bonus tickets from volunteering (default 3).",
"type": "number",
"minimum": 0
}
},
"required": [
"enabled"
],
"additionalProperties": false
},
"showCountsBeforeDrawing": {
"description": "Show applicants their ticket count on the confirmation and status page (default true). Turn off while rules are still changing.",
"type": "boolean"
}
},
"additionalProperties": false
},
"categories": {
"description": "Applicant categories, matched in order; an applicant lands in the first whose rule fits. Replaces the saved list when present. An empty list means one category: everyone.",
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 80,
"description": "Category name as runners see it: \"Veteran\", \"Never\", \"Women\"."
},
"key": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]{0,39}$",
"description": "Stable lowercase key (letters, digits, - and _). Defaults to a slug of the name; keep it when renaming so saved history stays attached."
},
"who": {
"description": "The plain audience this category holds. Leave out when `rule` says who belongs.",
"type": "string",
"enum": [
"everyone",
"veterans",
"first_timers",
"women",
"men"
]
},
"rule": {
"description": "A custom rule instead of `who`.",
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"everyone",
"finishes",
"starts",
"applications",
"selections",
"volunteer_years",
"gender",
"state",
"age"
],
"description": "What the rule counts: finishes or starts of this race, prior applications, prior selections (drawn in an earlier year), volunteer_years of service, the applicant's gender, home state, or age on race day."
},
"min": {
"description": "Lowest count or age that qualifies (inclusive). Null or absent: no lower bound.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"max": {
"description": "Highest count or age that qualifies (inclusive). \"max 0\" on finishes means never finished.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"values": {
"description": "For gender: \"female\", \"male\", \"other\", \"prefer_not_to_say\". For state: state codes or names.",
"maxItems": 80,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 60
}
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"also": {
"description": "Extra conditions every member must also meet (AND), e.g. a \"Never\" category that also requires no prior selection.",
"maxItems": 5,
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"everyone",
"finishes",
"starts",
"applications",
"selections",
"volunteer_years",
"gender",
"state",
"age"
],
"description": "What the rule counts: finishes or starts of this race, prior applications, prior selections (drawn in an earlier year), volunteer_years of service, the applicant's gender, home state, or age on race day."
},
"min": {
"description": "Lowest count or age that qualifies (inclusive). Null or absent: no lower bound.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"max": {
"description": "Highest count or age that qualifies (inclusive). \"max 0\" on finishes means never finished.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"values": {
"description": "For gender: \"female\", \"male\", \"other\", \"prefer_not_to_say\". For state: state codes or names.",
"maxItems": 80,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 60
}
}
},
"required": [
"kind"
],
"additionalProperties": false,
"description": "A membership rule for applicants, used when the plain audience words do not fit."
}
},
"spots": {
"description": "Spots this category's own drawing fills before the general drawing (a guaranteed minimum). Null or absent: none guaranteed; its applicants are drawn in the general pool.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"share": {
"description": "True: draw the category at no less than its share of applicants, worked out at draw time (\"women get the same share they applied in\"). Ignores `spots`.",
"type": "boolean"
},
"cap": {
"description": "The most entries this category may hold after the general drawing. Null: no limit.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"rolloverUnfilled": {
"description": "Unfilled guaranteed spots roll into the general drawing (default true).",
"type": "boolean"
}
},
"required": [
"name"
],
"additionalProperties": false
}
},
"reservedEntries": {
"description": "Entries held out of the drawing (director picks, tribal entries, a charity raffle). Replaces the saved list when present.",
"maxItems": 20,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 80,
"description": "Who the entries are held for, in the organizer's words (e.g. \"Director picks\", \"Sponsor entries\", \"Charity raffle\")."
},
"key": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]{0,39}$",
"description": "Stable lowercase key (letters, digits, - and _). Defaults to a slug of the name; keep it when renaming so saved history stays attached."
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "How many entries are held out of the drawing for them. Reserved entries come off capacity before the drawing picks its spots."
},
"fill": {
"type": "string",
"enum": [
"manual",
"invite_code",
"separate_draw"
],
"description": "How the spots are filled: \"manual\" (the organizer assigns each one), \"invite_code\" (an invitation code tied to this bucket admits them), \"separate_draw\" (a separate raffle or drawing picks them)."
},
"note": {
"description": "Who decides or why, shown to the organizer only.",
"anyOf": [
{
"type": "string",
"maxLength": 500
},
{
"type": "null"
}
]
}
},
"required": [
"name",
"count",
"fill"
],
"additionalProperties": false
}
},
"waitlist": {
"description": "Waitlist shape after the drawing.",
"type": "object",
"properties": {
"max": {
"description": "How many undrawn applicants are placed on the waitlist. Null (default): everyone not drawn, uncapped.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
]
},
"offerHours": {
"description": "Hours a waitlisted runner has to accept an offered spot, when different from the selection window.",
"anyOf": [
{
"type": "integer",
"minimum": 1,
"maximum": 720
},
{
"type": "null"
}
]
},
"offersCloseAt": {
"description": "After this local date-time no more waitlist offers go out.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d$",
"description": "A local date and time in the event's timezone, \"YYYY-MM-DDTHH:mm\" (2026-12-01T06:00 is 6:00 AM on December 1). Never an ISO instant with Z or an offset; EHQ converts it."
},
{
"type": "null"
}
]
},
"backfill": {
"description": "Who a released spot goes to first: \"gender\" (a woman's spot goes to the next woman waiting, a man's to the next man), \"pool\" (the next in the same category), \"order\" (the next on the waitlist, whoever it is).",
"type": "string",
"enum": [
"pool",
"gender",
"order"
]
},
"publishApplicantList": {
"description": "Publish the applicant list (names and status) on the event website.",
"type": "boolean"
}
},
"additionalProperties": false
},
"selectionPayment": {
"description": "How a drawn runner pays.",
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"respond",
"card_on_file"
],
"description": "\"respond\": drawn runners pay through a deadline link. \"card_on_file\": a card saved at application is charged after the notice window."
},
"cardChargeNoticeHours": {
"description": "card_on_file only: hours between the selection notice and the charge (default 48).",
"type": "integer",
"minimum": 1,
"maximum": 336
}
},
"required": [
"mode"
],
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"qualifier": {
"description": "The qualifying race runner requirement. Null removes it.",
"anyOf": [
{
"type": "object",
"properties": {
"name": {
"description": "Requirement name as runners see it (default \"Qualifying race\").",
"type": "string",
"minLength": 1,
"maxLength": 160
},
"description": {
"description": "Which races count, in prose, shown to applicants.",
"type": "string",
"maxLength": 8000
},
"windowStart": {
"description": "First date a qualifying race may fall on (inclusive). Null: open.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
{
"type": "null"
}
]
},
"windowEnd": {
"description": "Last date a qualifying race may fall on (inclusive). Null: open.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
{
"type": "null"
}
]
},
"verifiedOnly": {
"description": "True: only applications whose qualifier was verified (by an organizer or the automatic check) enter the drawing.",
"type": "boolean"
},
"autoVerify": {
"description": "True: an automatic \"meets\" finding from the standards check verifies the qualifier without review.",
"type": "boolean"
},
"standards": {
"description": "Checkable standards. Replaces the saved list when present.",
"maxItems": 12,
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"distance",
"race",
"list"
],
"description": "\"distance\": a course of at least a distance with gain and time bounds. \"race\": one named race. \"list\": a maintained list of races."
},
"minDistanceKm": {
"description": "distance: shortest course that counts, in kilometers (100K = 100, 100 miles = 160.9344).",
"type": "number",
"minimum": 0
},
"maxDistanceKm": {
"description": "distance: longest course that counts, in kilometers. Null: open.",
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "null"
}
]
},
"minGainM": {
"description": "distance: least elevation gain, in meters (10,000 ft = 3048; 15,000 ft = 4572).",
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "null"
}
]
},
"maxGainM": {
"description": "distance: most elevation gain, in meters. Null: open.",
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "null"
}
]
},
"maxTimeSeconds": {
"description": "Finish time cap in seconds (20 hours = 72000). Null: finishing inside the race's own cutoff is enough.",
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "null"
}
]
},
"raceName": {
"description": "race: the race's name (\"Copper Kings 100\").",
"type": "string",
"minLength": 1,
"maxLength": 160
},
"crewableSlug": {
"description": "race: Crewable directory slug when known.",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 120
},
{
"type": "null"
}
]
},
"ehqEventId": {
"description": "race: Endurance HQ event id when the race is on the platform.",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 64
},
{
"type": "null"
}
]
},
"listId": {
"description": "list: the list id (\"western-states-100\").",
"type": "string",
"minLength": 1,
"maxLength": 80
},
"label": {
"description": "The organizer's own words for this line, shown instead of the generated one.",
"anyOf": [
{
"type": "string",
"maxLength": 200
},
{
"type": "null"
}
]
}
},
"required": [
"kind"
],
"additionalProperties": false,
"description": "One checkable standard a qualifying race must meet."
}
},
"askedAt": {
"description": "When the qualifier is asked: \"during_registration\" (on the application; default) or \"after_registration\".",
"type": "string",
"enum": [
"during_registration",
"after_registration"
]
},
"raceOptionIds": {
"description": "Only these distances (race option ids) ask it. Empty or absent: every distance.",
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"trailWork": {
"description": "The trail work runner requirement. Null removes it.",
"anyOf": [
{
"type": "object",
"properties": {
"name": {
"description": "Requirement name as runners see it (default \"Trail work\").",
"type": "string",
"minLength": 1,
"maxLength": 160
},
"description": {
"description": "What counts and how to log it, in prose, shown to entrants.",
"type": "string",
"maxLength": 8000
},
"hours": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "Hours each entrant owes (8)."
},
"dueDate": {
"description": "Local calendar date the hours are due by (end of that day in the event's timezone). Null: no due date.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
{
"type": "null"
}
]
},
"buyoutCents": {
"description": "Donation that satisfies the requirement instead of hours, in cents ($250 = 25000). Null: no buyout.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "Money in cents: $250 is 25000, free is 0."
},
{
"type": "null"
}
]
},
"beneficiary": {
"description": "Where buyout donations go. Null clears it.",
"anyOf": [
{
"type": "object",
"properties": {
"organizationId": {
"description": "Saved donation organization id when known.",
"type": "string",
"minLength": 1,
"maxLength": 64
},
"name": {
"description": "Trail group that receives buyout donations, by name. Created on the event when it does not exist yet.",
"type": "string",
"minLength": 1,
"maxLength": 160
},
"donationUrl": {
"description": "The group's own donation page, when donations are paid there.",
"type": "string",
"format": "uri"
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"workWindowStart": {
"description": "Earliest work date that counts (inclusive). Null: any date.",
"anyOf": [
{
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
{
"type": "null"
}
]
},
"accepts": {
"description": "The kinds of work that count toward the hours (default: all three).",
"type": "object",
"properties": {
"organizationDays": {
"type": "boolean",
"description": "Work on trail days the organization runs counts."
},
"outsideTrailWork": {
"type": "boolean",
"description": "Trail work with another group, confirmed by that group's coordinator, counts."
},
"outsideVolunteering": {
"type": "boolean",
"description": "Volunteering at another race, confirmed by its director, counts."
}
},
"required": [
"organizationDays",
"outsideTrailWork",
"outsideVolunteering"
],
"additionalProperties": false
},
"organizationDays": {
"description": "Trail days the organization runs. Replaces the saved list when present.",
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"description": "Stable id; defaults to a slug of the date and name.",
"type": "string",
"minLength": 1,
"maxLength": 64
},
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "A calendar date in the event's timezone, \"YYYY-MM-DD\"."
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "What the day is called."
},
"location": {
"anyOf": [
{
"type": "string",
"maxLength": 160
},
{
"type": "null"
}
]
},
"hours": {
"description": "Hours a full day counts for; the runner can log fewer.",
"anyOf": [
{
"type": "number",
"minimum": 0.25,
"maximum": 24
},
{
"type": "null"
}
]
}
},
"required": [
"date",
"name"
],
"additionalProperties": false
}
},
"directorReview": {
"description": "True: a coordinator's confirmation still waits for the director before the hours count (default false).",
"type": "boolean"
},
"deadline": {
"description": "What happens to entrants who have not done the hours by the due date.",
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"flag",
"release_spot"
],
"description": "At the due date: \"flag\" only lists overdue entrants for the director; \"release_spot\" drops them from the field so the spot goes to the waitlist."
},
"graceDays": {
"description": "Days after the due date before the action runs (default 0).",
"type": "integer",
"minimum": 0,
"maximum": 60
},
"requires": {
"description": "What counts as done at the deadline: \"logged\" (enough hours logged, verification may still be pending) or \"verified\" (hours verified).",
"type": "string",
"enum": [
"logged",
"verified"
]
},
"notifyRunner": {
"description": "Tell the runner when the action runs. False drops them silently; the scheduled reminders still go out beforehand.",
"type": "boolean"
},
"lateAdmitDays": {
"description": "Days an entrant admitted after the due date gets, counted from their admission, before the deadline action applies to them. 0 or null: the next run treats them like everyone else.",
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 365
},
{
"type": "null"
}
]
}
},
"required": [
"action"
],
"additionalProperties": false
},
"showVerifiedOnApplicantList": {
"description": "Show a \"Trail work verified\" check beside accepted entrants on the public list (default false).",
"type": "boolean"
},
"reminders": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Send the shared requirement reminders."
},
"daysBefore": {
"description": "Days before the due date a reminder goes out (default 30, 14, 7, 1).",
"maxItems": 12,
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 365
}
}
},
"required": [
"enabled"
],
"additionalProperties": false
},
"raceOptionIds": {
"description": "Only entrants in these distances owe it. Empty or absent: every distance.",
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
}
},
"required": [
"hours"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"questions": {
"description": "Registration questions, matched to saved ones by label. Saved questions not listed are left alone.",
"maxItems": 100,
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The question as the form asks it (\"Would you like a pacer?\"). Matches a saved question by label."
},
"type": {
"description": "\"checkbox\" for a yes/no, \"select\" one of the options, \"multiselect\" several, \"number\", \"textarea\" for long text, \"text\" otherwise (default).",
"type": "string",
"enum": [
"text",
"select",
"multiselect",
"checkbox",
"number",
"textarea"
]
},
"options": {
"description": "Answer choices for select and multiselect.",
"anyOf": [
{
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
{
"type": "null"
}
]
},
"required": {
"description": "Whether the runner must answer (default false).",
"type": "boolean"
},
"askedAt": {
"description": "\"during_registration\" asks at checkout or on the application (default); \"after_registration\" asks in the event app afterwards.",
"type": "string",
"enum": [
"during_registration",
"after_registration"
]
},
"visibility": {
"anyOf": [
{
"type": "object",
"properties": {
"minAgeOnRaceDay": {
"description": "Ask only runners at least this old on race day (65 for a 65+ pacer request).",
"type": "integer",
"minimum": 0,
"maximum": 130
},
"maxAgeOnRaceDay": {
"description": "Ask only runners at most this old on race day.",
"type": "integer",
"minimum": 0,
"maximum": 130
},
"raceOptionIds": {
"description": "Ask only on these distances (race option ids).",
"maxItems": 100,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
},
"answerEquals": {
"description": "Ask only when another question has this answer.",
"type": "object",
"properties": {
"questionLabel": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The other question's label."
},
"value": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "The answer that reveals this question (\"true\" for a checked box)."
}
},
"required": [
"questionLabel",
"value"
],
"additionalProperties": false
}
},
"additionalProperties": false,
"description": "Who is asked; every rule present must hold. Absent or null: everyone."
},
{
"type": "null"
}
]
},
"autoFillSource": {
"description": "Prefill a number question from EHQ data (volunteer years of service). Answers are always organizer-only; nothing a runner answers here is published.",
"anyOf": [
{
"type": "string",
"enum": [
"volunteer_years_of_service"
]
},
{
"type": "null"
}
]
},
"remove": {
"description": "True removes the saved question with this label.",
"type": "boolean"
}
},
"required": [
"label"
],
"additionalProperties": false
}
},
"addOns": {
"description": "Things for sale at registration, matched to saved add-ons by title.",
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "What is for sale at registration (\"Camping spot, church lot\"). Matches a saved add-on by title."
},
"description": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
},
"priceCents": {
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "Money in cents: $250 is 25000, free is 0."
},
{
"type": "null"
}
],
"description": "Price per unit in cents. Null when the price isn't decided yet: applying asks the organizer for it as a quick answer and puts the add-on on sale once they reply."
},
"kind": {
"description": "\"service\" for a spot, reservation or seat (default); \"merch\" for a physical item.",
"type": "string",
"enum": [
"service",
"merch"
]
},
"inventory": {
"description": "Stock for this add-on. Null: unlimited.",
"anyOf": [
{
"type": "object",
"properties": {
"total": {
"anyOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "Units for sale in total (7 camping spots). EHQ counts what is left as runners buy and shows when it is full. Null: not tracked."
}
},
"required": [
"total"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"namedSpots": {
"description": "Each unit is a specific spot the organizer assigns (camping sites), with live availability on the form.",
"type": "boolean"
},
"salesChannel": {
"description": "Where it is offered: \"registration_only\" at registration (default), \"store_and_registration\" in the store as well, \"registered_runners_only\" in the event app shop to runners holding a confirmed entry (camping bought after a lottery acceptance), never on the public form.",
"type": "string",
"enum": [
"registration_only",
"store_and_registration",
"registered_runners_only"
]
},
"published": {
"description": "Live for sale (default true). False keeps it a draft.",
"type": "boolean"
},
"remove": {
"description": "True archives the saved add-on with this title.",
"type": "boolean"
}
},
"required": [
"title",
"priceCents"
],
"additionalProperties": false
}
},
"volunteerPositions": {
"description": "Volunteer roles, matched by name.",
"maxItems": 100,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"description": "Role name (\"Aid station captain\", \"Ham radio operator\"). Matches a saved role by name."
},
"description": {
"description": "What the role does, shown to volunteers.",
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
},
"category": {
"description": "What the role is, independent of its name, so captain-only audiences and station cards find it.",
"anyOf": [
{
"type": "string",
"enum": [
"pre_race_course_check",
"aid_station_captain",
"aid_station_team",
"course_monitor",
"check_in",
"video_content",
"finish_area",
"course_sweep",
"setup_teardown",
"parking",
"medical",
"radio",
"course_marking",
"other"
]
},
{
"type": "null"
}
]
},
"active": {
"description": "False hides the role from signups without deleting it.",
"type": "boolean"
},
"communicationPolicy": {
"description": "Whether people in this role receive event-wide volunteer messages.",
"type": "string",
"enum": [
"inherit",
"include",
"exclude"
]
}
},
"required": [
"name"
],
"additionalProperties": false
}
},
"automations": {
"description": "Recurring or anchored messages, matched by name.",
"maxItems": 50,
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "Automation name (\"Trail work reminder\"). Matches a saved custom automation by name."
},
"anchor": {
"type": "string",
"enum": [
"event_date",
"registration",
"calendar_date"
],
"description": "What the send time counts from: \"event_date\" (the race date), \"registration\" (each runner's registration), or \"calendar_date\" (a fixed month and day every year)."
},
"offsetDays": {
"description": "Days relative to the anchor; negative is before (event_date and -7 sends a week before the race). Registration sends at or after registration only.",
"type": "integer",
"minimum": -365,
"maximum": 365
},
"offsetMinutes": {
"description": "Minutes relative to the anchor when days are too coarse. Added to offsetDays.",
"type": "integer",
"minimum": -525600,
"maximum": 525600
},
"calendarDate": {
"description": "calendar_date only: the date each year (April 20 for an annual trail work reminder).",
"type": "object",
"properties": {
"month": {
"type": "integer",
"minimum": 1,
"maximum": 12,
"description": "Month, 1 to 12."
},
"day": {
"type": "integer",
"minimum": 1,
"maximum": 31,
"description": "Day of the month."
},
"time": {
"description": "Local send time \"HH:mm\" (default 09:00).",
"type": "string",
"pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$"
}
},
"required": [
"month",
"day"
],
"additionalProperties": false
},
"keepOnEachEdition": {
"description": "Carry this automation to each new edition of the race so it is set once and lightly edited each year.",
"type": "boolean"
},
"audience": {
"description": "Who receives it (default participants).",
"type": "string",
"enum": [
"participants",
"volunteers"
]
},
"segment": {
"description": "A saved smart segment key instead of the built-in audience.",
"type": "string",
"minLength": 1,
"maxLength": 120
},
"list": {
"description": "A saved contact list id instead of the built-in audience.",
"type": "string",
"minLength": 1,
"maxLength": 120
},
"channels": {
"description": "Delivery channels (default email).",
"minItems": 1,
"type": "array",
"items": {
"type": "string",
"enum": [
"email",
"sms",
"push"
]
}
},
"subject": {
"type": "string",
"minLength": 1,
"maxLength": 300,
"description": "Subject line."
},
"body": {
"type": "string",
"minLength": 1,
"maxLength": 100000,
"description": "Message body."
},
"reviewMode": {
"description": "\"auto\" sends on schedule; \"notify\" tells the organizer first; \"hold\" waits for the organizer to release it (default hold for event-date sends).",
"type": "string",
"enum": [
"auto",
"notify",
"hold"
]
},
"reviewNoticeDays": {
"description": "Days before the send the review notice goes out (default 3).",
"type": "integer",
"minimum": 1,
"maximum": 30
},
"active": {
"description": "Turned on (default false). Activating schedules real sends.",
"type": "boolean"
},
"remove": {
"description": "True turns the saved automation with this name off (sends stop; history is kept).",
"type": "boolean"
}
},
"required": [
"name",
"anchor",
"subject",
"body"
],
"additionalProperties": false
}
},
"notes": {
"description": "Open decisions to flag for the organizer rather than guess (\"Veteran ticket per finish still undecided\").",
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
}
},
"additionalProperties": false,
"description": "A race's registration rules in plain terms. Absent sections are left as they are."
},
"summary": {
"type": "string"
},
"sources": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"event",
"ruleset",
"summary",
"sources"
],
"additionalProperties": false
}
},
"required": [
"data"
],
"additionalProperties": false
}Other surfaces
CLI: ehq registration rules get --event <event_id>
MCP tool: ehq_registration_rules_get