> For the complete documentation index, see [llms.txt](https://movemint.gitbook.io/movemint-developer-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://movemint.gitbook.io/movemint-developer-docs/open-api-specification/core-resources/crm.md).

# CRM

Organizer-level CRM surface used by Movemint AI. Records of every kind (participants, external contacts, donations, volunteers, waitlisters, finishers, merchandise orders) are addressed by their `crm_entity_id`, not nested under an event. Access is per record: on the event the record belongs to, the caller must both hold the CRM scope that governs that record's kind (`crm_participants`, `crm_donations`, …) **and** have the AI-assistant (`ai_chat`) capability enabled. A record on an event without either returns `403 entity_not_permitted`.

Record access is deliberately scoped to lookup and **private notes** only — there is no endpoint to edit or delete a record, and notes are append-only (no update or delete). There is no bulk endpoint; each call acts on one record. The tag also covers the athlete-scoped **modes** and **columns** surface (saved CRM views): the columns catalog plus create / update / delete / hide-show of the caller's own custom modes.

## Search / list CRM records

> Search and filter the caller's CRM records by free text (\`q\`) and/or\
> the same structured filters the CRM admin grid offers — record kind,\
> gender, created/updated date ranges, waitlister status, tags,\
> registration source, discount codes, check-in / waiver / athlete-account\
> / merchandise-fulfilment / has-notes flags, donation-amount and loyalty\
> (editions / same-distance) thresholds, and relational id filters (events,\
> sub-events, merchandise, volunteer roles, volunteer time slots, waves,\
> question options).\
> Every parameter is optional; filters may be used without \`q\`.\
> Relational filters take record ids from the matching list endpoint.\
> Returns up to 25 matches (opt into more via \`limit\`, capped at 100), each\
> with its \`crm\_entity\_id\`. Results are limited to record kinds the caller's\
> CRM scopes permit, on events the assistant is enabled on.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"CrmQ":{"name":"q","in":"query","required":false,"description":"Free-text — name, email, or confirmation number.","schema":{"type":"string"}},"CrmEntityKind":{"name":"entity_kind","in":"query","required":false,"description":"Restrict to a single record kind.","schema":{"type":"string","enum":["EventParticipants","RemovedParticipants","GroupExternalContacts","EventExternalContacts","AthleteExternalContacts","Donations","Volunteers","MerchandiseOrders","Waitlisters","Finishers","InvitedParticipants"]}},"CrmGender":{"name":"gender","in":"query","required":false,"description":"Gender identity to filter by, e.g. \"male\", \"female\", \"nonbinary\".","schema":{"type":"string"}},"CrmCreatedAfter":{"name":"created_after","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records created on or after this day.","schema":{"type":"string","format":"date"}},"CrmCreatedBefore":{"name":"created_before","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records created on or before this day.","schema":{"type":"string","format":"date"}},"CrmWaitlisterStatus":{"name":"waitlister_status","in":"query","required":false,"description":"Waitlisters only: filter by status. An invited or accepted person is no longer a waitlister.","schema":{"type":"string","enum":["WAITING","EXPIRED_INVITE"]}},"CrmTags":{"name":"tags","in":"query","required":false,"description":"Records carrying any of these CRM tags.","schema":{"type":"array","items":{"type":"string"}}},"CrmRegistrationSources":{"name":"registration_sources","in":"query","required":false,"description":"Participants only — how they registered (e.g. \"Instagram\", \"Direct\"). Any listed source matches.","schema":{"type":"array","items":{"type":"string"}}},"CrmDiscountCodes":{"name":"discount_codes","in":"query","required":false,"description":"Participants who redeemed any of these discount codes at checkout.","schema":{"type":"array","items":{"type":"string"}}},"CrmCheckedIn":{"name":"checked_in","in":"query","required":false,"description":"Participants only — true = checked in on race day, false = not checked in.","schema":{"type":"boolean"}},"CrmWaiverSigned":{"name":"waiver_signed","in":"query","required":false,"description":"true = has signed all required waivers, false = has an unsigned waiver.","schema":{"type":"boolean"}},"CrmHasAthleteAccount":{"name":"has_athlete_account","in":"query","required":false,"description":"Participants only — true = linked to an athlete account, false = guest checkout.","schema":{"type":"boolean"}},"CrmMerchandiseFulfilled":{"name":"merchandise_fulfilled","in":"query","required":false,"description":"Merchandise orders only — true = fulfilled, false = unfulfilled.","schema":{"type":"boolean"}},"CrmHasNotes":{"name":"has_notes","in":"query","required":false,"description":"Pass true to keep only records with at least one private internal note. false/omitted does not filter.","schema":{"type":"boolean"}},"CrmUpdatedAfter":{"name":"updated_after","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records last updated on or after this day.","schema":{"type":"string","format":"date"}},"CrmUpdatedBefore":{"name":"updated_before","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records last updated on or before this day.","schema":{"type":"string","format":"date"}},"CrmMinDonatedAmount":{"name":"min_donated_amount","in":"query","required":false,"description":"Keep records whose donation total is more than this many whole dollars.","schema":{"type":"integer","minimum":1}},"CrmMaxDonatedAmount":{"name":"max_donated_amount","in":"query","required":false,"description":"Keep records whose donation total is less than this many whole dollars.","schema":{"type":"integer","minimum":1}},"CrmMinEditionsParticipated":{"name":"min_editions_participated","in":"query","required":false,"description":"Participants only — keep people who have registered for at least this\nmany editions of the same recurring event (an EditionGroup). Pass 2 for\nrepeat/returning participants. Identity is matched across editions by\nlinked athlete account, falling back to email.\n","schema":{"type":"integer","minimum":2}},"CrmMinSameDistanceEntries":{"name":"min_same_distance_entries","in":"query","required":false,"description":"Participants only — like min_editions_participated, but counting only\neditions run at the same distance. Pass 2 for people who have done a\ngiven distance more than once.\n","schema":{"type":"integer","minimum":2}},"CrmEvents":{"name":"events","in":"query","required":false,"description":"Restrict to records on these event ids. Narrows within the events the caller administers.","schema":{"type":"array","items":{"type":"integer"}}},"CrmSubEvents":{"name":"sub_events","in":"query","required":false,"description":"Restrict to registrations for these sub-event (distance/category) ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmMerchandises":{"name":"merchandises","in":"query","required":false,"description":"Records who ordered any of these merchandise item ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmVolunteerCategories":{"name":"volunteer_categories","in":"query","required":false,"description":"Volunteers in any of these volunteer role/category ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmVolunteerTimeSlots":{"name":"volunteer_time_slots","in":"query","required":false,"description":"Volunteers signed up for any of these volunteer time-slot (shift) ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmWaves":{"name":"waves","in":"query","required":false,"description":"Participants assigned to any of these start-wave ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmQuestionOptionIds":{"name":"question_option_ids","in":"query","required":false,"description":"Participants who chose any of these multiple-choice question option ids.","schema":{"type":"array","items":{"type":"integer"}}}},"schemas":{"CrmEntitySearchResult":{"type":"object","required":["entities"],"properties":{"entities":{"type":"array","items":{"$ref":"#/components/schemas/CrmEntitySummary"}}}},"CrmEntitySummary":{"type":"object","description":"A single CRM record returned by search.","required":["crm_entity_id","entity_kind","full_name","email","event_handle","event_name","tags","has_notes"],"properties":{"crm_entity_id":{"type":"integer","format":"int64"},"entity_kind":{"type":"string","description":"The record's model kind (EventParticipant, ExternalContact, Donation, …)."},"full_name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"event_handle":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"has_notes":{"type":"boolean"}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CrmInvalidQueryParams":{"description":"A query parameter is invalid: `entity_kind` is not a supported kind\n(`error: invalid_entity_kind`), a date param (`created_after`,\n`created_before`, `updated_after`, `updated_before`) is not a parseable\ndate (`error: invalid_date`, with the offending `param`), a numeric\nthreshold (`min_editions_participated`, `min_same_distance_entries`,\n`min_donated_amount`, `max_donated_amount`) is not a whole number at or\nabove its floor (`error: invalid_<param>`, with the offending `param`),\nor an array param carries more than `max` values (`error:\ntoo_many_values`, with the offending `param` and the `max`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/entities":{"get":{"tags":["CRM"],"summary":"Search / list CRM records","description":"Search and filter the caller's CRM records by free text (`q`) and/or\nthe same structured filters the CRM admin grid offers — record kind,\ngender, created/updated date ranges, waitlister status, tags,\nregistration source, discount codes, check-in / waiver / athlete-account\n/ merchandise-fulfilment / has-notes flags, donation-amount and loyalty\n(editions / same-distance) thresholds, and relational id filters (events,\nsub-events, merchandise, volunteer roles, volunteer time slots, waves,\nquestion options).\nEvery parameter is optional; filters may be used without `q`.\nRelational filters take record ids from the matching list endpoint.\nReturns up to 25 matches (opt into more via `limit`, capped at 100), each\nwith its `crm_entity_id`. Results are limited to record kinds the caller's\nCRM scopes permit, on events the assistant is enabled on.\n","operationId":"searchCrmEntities","parameters":[{"$ref":"#/components/parameters/CrmQ"},{"$ref":"#/components/parameters/CrmEntityKind"},{"$ref":"#/components/parameters/CrmGender"},{"$ref":"#/components/parameters/CrmCreatedAfter"},{"$ref":"#/components/parameters/CrmCreatedBefore"},{"$ref":"#/components/parameters/CrmWaitlisterStatus"},{"$ref":"#/components/parameters/CrmTags"},{"$ref":"#/components/parameters/CrmRegistrationSources"},{"$ref":"#/components/parameters/CrmDiscountCodes"},{"$ref":"#/components/parameters/CrmCheckedIn"},{"$ref":"#/components/parameters/CrmWaiverSigned"},{"$ref":"#/components/parameters/CrmHasAthleteAccount"},{"$ref":"#/components/parameters/CrmMerchandiseFulfilled"},{"$ref":"#/components/parameters/CrmHasNotes"},{"$ref":"#/components/parameters/CrmUpdatedAfter"},{"$ref":"#/components/parameters/CrmUpdatedBefore"},{"$ref":"#/components/parameters/CrmMinDonatedAmount"},{"$ref":"#/components/parameters/CrmMaxDonatedAmount"},{"$ref":"#/components/parameters/CrmMinEditionsParticipated"},{"$ref":"#/components/parameters/CrmMinSameDistanceEntries"},{"$ref":"#/components/parameters/CrmEvents"},{"$ref":"#/components/parameters/CrmSubEvents"},{"$ref":"#/components/parameters/CrmMerchandises"},{"$ref":"#/components/parameters/CrmVolunteerCategories"},{"$ref":"#/components/parameters/CrmVolunteerTimeSlots"},{"$ref":"#/components/parameters/CrmWaves"},{"$ref":"#/components/parameters/CrmQuestionOptionIds"},{"name":"limit","in":"query","required":false,"description":"Maximum number of matches to return. Defaults to 25 (matching the\nCRM admin grid's page size); values above 100 are clamped to 100.\n","schema":{"type":"integer","default":25,"maximum":100}}],"responses":{"200":{"description":"Matching records (may be empty)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmEntitySearchResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/CrmInvalidQueryParams"},"503":{"description":"Search backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Count CRM records

> Count the records matching the same filters as the search endpoint,\
> without listing them (for "how many …" questions). The count already\
> respects the caller's per-kind CRM scopes and the events the assistant\
> is enabled on.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"CrmQ":{"name":"q","in":"query","required":false,"description":"Free-text — name, email, or confirmation number.","schema":{"type":"string"}},"CrmEntityKind":{"name":"entity_kind","in":"query","required":false,"description":"Restrict to a single record kind.","schema":{"type":"string","enum":["EventParticipants","RemovedParticipants","GroupExternalContacts","EventExternalContacts","AthleteExternalContacts","Donations","Volunteers","MerchandiseOrders","Waitlisters","Finishers","InvitedParticipants"]}},"CrmGender":{"name":"gender","in":"query","required":false,"description":"Gender identity to filter by, e.g. \"male\", \"female\", \"nonbinary\".","schema":{"type":"string"}},"CrmCreatedAfter":{"name":"created_after","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records created on or after this day.","schema":{"type":"string","format":"date"}},"CrmCreatedBefore":{"name":"created_before","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records created on or before this day.","schema":{"type":"string","format":"date"}},"CrmWaitlisterStatus":{"name":"waitlister_status","in":"query","required":false,"description":"Waitlisters only: filter by status. An invited or accepted person is no longer a waitlister.","schema":{"type":"string","enum":["WAITING","EXPIRED_INVITE"]}},"CrmTags":{"name":"tags","in":"query","required":false,"description":"Records carrying any of these CRM tags.","schema":{"type":"array","items":{"type":"string"}}},"CrmRegistrationSources":{"name":"registration_sources","in":"query","required":false,"description":"Participants only — how they registered (e.g. \"Instagram\", \"Direct\"). Any listed source matches.","schema":{"type":"array","items":{"type":"string"}}},"CrmDiscountCodes":{"name":"discount_codes","in":"query","required":false,"description":"Participants who redeemed any of these discount codes at checkout.","schema":{"type":"array","items":{"type":"string"}}},"CrmCheckedIn":{"name":"checked_in","in":"query","required":false,"description":"Participants only — true = checked in on race day, false = not checked in.","schema":{"type":"boolean"}},"CrmWaiverSigned":{"name":"waiver_signed","in":"query","required":false,"description":"true = has signed all required waivers, false = has an unsigned waiver.","schema":{"type":"boolean"}},"CrmHasAthleteAccount":{"name":"has_athlete_account","in":"query","required":false,"description":"Participants only — true = linked to an athlete account, false = guest checkout.","schema":{"type":"boolean"}},"CrmMerchandiseFulfilled":{"name":"merchandise_fulfilled","in":"query","required":false,"description":"Merchandise orders only — true = fulfilled, false = unfulfilled.","schema":{"type":"boolean"}},"CrmHasNotes":{"name":"has_notes","in":"query","required":false,"description":"Pass true to keep only records with at least one private internal note. false/omitted does not filter.","schema":{"type":"boolean"}},"CrmUpdatedAfter":{"name":"updated_after","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records last updated on or after this day.","schema":{"type":"string","format":"date"}},"CrmUpdatedBefore":{"name":"updated_before","in":"query","required":false,"description":"ISO date (YYYY-MM-DD); records last updated on or before this day.","schema":{"type":"string","format":"date"}},"CrmMinDonatedAmount":{"name":"min_donated_amount","in":"query","required":false,"description":"Keep records whose donation total is more than this many whole dollars.","schema":{"type":"integer","minimum":1}},"CrmMaxDonatedAmount":{"name":"max_donated_amount","in":"query","required":false,"description":"Keep records whose donation total is less than this many whole dollars.","schema":{"type":"integer","minimum":1}},"CrmMinEditionsParticipated":{"name":"min_editions_participated","in":"query","required":false,"description":"Participants only — keep people who have registered for at least this\nmany editions of the same recurring event (an EditionGroup). Pass 2 for\nrepeat/returning participants. Identity is matched across editions by\nlinked athlete account, falling back to email.\n","schema":{"type":"integer","minimum":2}},"CrmMinSameDistanceEntries":{"name":"min_same_distance_entries","in":"query","required":false,"description":"Participants only — like min_editions_participated, but counting only\neditions run at the same distance. Pass 2 for people who have done a\ngiven distance more than once.\n","schema":{"type":"integer","minimum":2}},"CrmEvents":{"name":"events","in":"query","required":false,"description":"Restrict to records on these event ids. Narrows within the events the caller administers.","schema":{"type":"array","items":{"type":"integer"}}},"CrmSubEvents":{"name":"sub_events","in":"query","required":false,"description":"Restrict to registrations for these sub-event (distance/category) ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmMerchandises":{"name":"merchandises","in":"query","required":false,"description":"Records who ordered any of these merchandise item ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmVolunteerCategories":{"name":"volunteer_categories","in":"query","required":false,"description":"Volunteers in any of these volunteer role/category ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmVolunteerTimeSlots":{"name":"volunteer_time_slots","in":"query","required":false,"description":"Volunteers signed up for any of these volunteer time-slot (shift) ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmWaves":{"name":"waves","in":"query","required":false,"description":"Participants assigned to any of these start-wave ids.","schema":{"type":"array","items":{"type":"integer"}}},"CrmQuestionOptionIds":{"name":"question_option_ids","in":"query","required":false,"description":"Participants who chose any of these multiple-choice question option ids.","schema":{"type":"array","items":{"type":"integer"}}}},"schemas":{"CrmCountResult":{"type":"object","required":["count"],"properties":{"count":{"type":"integer","description":"Number of records matching the filters."}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CrmInvalidQueryParams":{"description":"A query parameter is invalid: `entity_kind` is not a supported kind\n(`error: invalid_entity_kind`), a date param (`created_after`,\n`created_before`, `updated_after`, `updated_before`) is not a parseable\ndate (`error: invalid_date`, with the offending `param`), a numeric\nthreshold (`min_editions_participated`, `min_same_distance_entries`,\n`min_donated_amount`, `max_donated_amount`) is not a whole number at or\nabove its floor (`error: invalid_<param>`, with the offending `param`),\nor an array param carries more than `max` values (`error:\ntoo_many_values`, with the offending `param` and the `max`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/entities/count":{"get":{"tags":["CRM"],"summary":"Count CRM records","description":"Count the records matching the same filters as the search endpoint,\nwithout listing them (for \"how many …\" questions). The count already\nrespects the caller's per-kind CRM scopes and the events the assistant\nis enabled on.\n","operationId":"countCrmEntities","parameters":[{"$ref":"#/components/parameters/CrmQ"},{"$ref":"#/components/parameters/CrmEntityKind"},{"$ref":"#/components/parameters/CrmGender"},{"$ref":"#/components/parameters/CrmCreatedAfter"},{"$ref":"#/components/parameters/CrmCreatedBefore"},{"$ref":"#/components/parameters/CrmWaitlisterStatus"},{"$ref":"#/components/parameters/CrmTags"},{"$ref":"#/components/parameters/CrmRegistrationSources"},{"$ref":"#/components/parameters/CrmDiscountCodes"},{"$ref":"#/components/parameters/CrmCheckedIn"},{"$ref":"#/components/parameters/CrmWaiverSigned"},{"$ref":"#/components/parameters/CrmHasAthleteAccount"},{"$ref":"#/components/parameters/CrmMerchandiseFulfilled"},{"$ref":"#/components/parameters/CrmHasNotes"},{"$ref":"#/components/parameters/CrmUpdatedAfter"},{"$ref":"#/components/parameters/CrmUpdatedBefore"},{"$ref":"#/components/parameters/CrmMinDonatedAmount"},{"$ref":"#/components/parameters/CrmMaxDonatedAmount"},{"$ref":"#/components/parameters/CrmMinEditionsParticipated"},{"$ref":"#/components/parameters/CrmMinSameDistanceEntries"},{"$ref":"#/components/parameters/CrmEvents"},{"$ref":"#/components/parameters/CrmSubEvents"},{"$ref":"#/components/parameters/CrmMerchandises"},{"$ref":"#/components/parameters/CrmVolunteerCategories"},{"$ref":"#/components/parameters/CrmVolunteerTimeSlots"},{"$ref":"#/components/parameters/CrmWaves"},{"$ref":"#/components/parameters/CrmQuestionOptionIds"}],"responses":{"200":{"description":"The number of matching records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmCountResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/CrmInvalidQueryParams"},"503":{"description":"Search backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Get a record's full current detail

> Returns the record's present state — the fields that apply to its kind\
> (identity, contact, event/sub-event, bib/chip, wave, checked-in status,\
> tags, waiver status, payment info, kind-specific fields) plus\
> \`custom\_answers\` (the registration-question answers, keyed by question\
> title). Read-only: money is readable but never mutated here. Absent\
> fields are omitted, so the key set varies by record kind.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"CrmEntityId":{"name":"crm_entity_id","in":"path","required":true,"description":"The CRM entity id, from the search endpoint.","schema":{"type":"integer","format":"int64"}}},"schemas":{"CrmEntityDetailResult":{"type":"object","required":["entity"],"properties":{"entity":{"type":"object","description":"The record's current fields. `entity_kind` and `crm_entity_id` are\nalways present; every other key is included only when it applies to\nthe record's kind and has a value, so the key set varies by kind.\n","required":["entity_kind","crm_entity_id"],"properties":{"entity_kind":{"type":"string"},"crm_entity_id":{"type":"integer","format":"int64"},"custom_answers":{"type":"object","description":"Registration-question answers, keyed by question title.","additionalProperties":{"type":"string"}}},"additionalProperties":true}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"CrmEntityForbidden":{"description":"The caller's CRM scopes do not permit acting on this record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CrmEntityNotFound":{"description":"CRM entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/entities/{crm_entity_id}":{"get":{"tags":["CRM"],"summary":"Get a record's full current detail","description":"Returns the record's present state — the fields that apply to its kind\n(identity, contact, event/sub-event, bib/chip, wave, checked-in status,\ntags, waiver status, payment info, kind-specific fields) plus\n`custom_answers` (the registration-question answers, keyed by question\ntitle). Read-only: money is readable but never mutated here. Absent\nfields are omitted, so the key set varies by record kind.\n","operationId":"getCrmEntity","parameters":[{"$ref":"#/components/parameters/CrmEntityId"}],"responses":{"200":{"description":"The record's current detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmEntityDetailResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CrmEntityForbidden"},"404":{"$ref":"#/components/responses/CrmEntityNotFound"},"422":{"description":"The record's underlying data could not be loaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## List a record's private notes

> Returns the record's private notes oldest-first, capped at the 100 most\
> recent (older notes past the cap are omitted).<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"CrmEntityId":{"name":"crm_entity_id","in":"path","required":true,"description":"The CRM entity id, from the search endpoint.","schema":{"type":"integer","format":"int64"}}},"schemas":{"CrmNoteListResult":{"type":"object","required":["notes"],"properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/CrmNote"}}}},"CrmNote":{"type":"object","required":["id","body","note_type","author_name","created_at"],"properties":{"id":{"type":"integer","format":"int64"},"body":{"type":"string"},"note_type":{"type":"string","enum":["general","refund"]},"author_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"CrmEntityForbidden":{"description":"The caller's CRM scopes do not permit acting on this record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CrmEntityNotFound":{"description":"CRM entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/entities/{crm_entity_id}/notes":{"get":{"tags":["CRM"],"summary":"List a record's private notes","description":"Returns the record's private notes oldest-first, capped at the 100 most\nrecent (older notes past the cap are omitted).\n","operationId":"listCrmNotes","parameters":[{"$ref":"#/components/parameters/CrmEntityId"}],"responses":{"200":{"description":"The record's notes, oldest first (most recent 100)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmNoteListResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CrmEntityForbidden"},"404":{"$ref":"#/components/responses/CrmEntityNotFound"}}}}}}
````

## Add a private note to a record

> Appends a team-only internal note. Notes are never shown to the\
> participant, and are append-only — there is no update or delete.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"CrmEntityId":{"name":"crm_entity_id","in":"path","required":true,"description":"The CRM entity id, from the search endpoint.","schema":{"type":"integer","format":"int64"}}},"schemas":{"CreateCrmNoteRequest":{"type":"object","required":["body"],"properties":{"body":{"type":"string","description":"The note text (internal, team-only)."}}},"CrmNoteResult":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/CrmNote"}}},"CrmNote":{"type":"object","required":["id","body","note_type","author_name","created_at"],"properties":{"id":{"type":"integer","format":"int64"},"body":{"type":"string"},"note_type":{"type":"string","enum":["general","refund"]},"author_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}},"ValidationError":{"type":"object","description":"A validation error response with a list of error messages","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"string"},"description":"List of validation error messages"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"CrmEntityForbidden":{"description":"The caller's CRM scopes do not permit acting on this record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CrmEntityNotFound":{"description":"CRM entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/entities/{crm_entity_id}/notes":{"post":{"tags":["CRM"],"summary":"Add a private note to a record","description":"Appends a team-only internal note. Notes are never shown to the\nparticipant, and are append-only — there is no update or delete.\n","operationId":"createCrmNote","parameters":[{"$ref":"#/components/parameters/CrmEntityId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCrmNoteRequest"}}}},"responses":{"201":{"description":"Note created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmNoteResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CrmEntityForbidden"},"404":{"$ref":"#/components/responses/CrmEntityNotFound"},"422":{"description":"Missing or invalid note body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}}}}
````

## List a merged activity timeline across many records

> Merges the activity history of several CRM records into one feed,\
> newest-first — for answering "what's the history of \<person>?" in a\
> single call when a person spans multiple records (one per\
> event/record-kind). Each entry is tagged with its \`crm\_entity\_id\` and\
> \`event\_name\`, and the \`entities\` block maps every id to its\
> name/kind/event. \`has\_more\` is true when older entries were trimmed past\
> the global cap. Ids the caller can't access are dropped and returned in\
> \`unavailable\_crm\_entity\_ids\`.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"schemas":{"CrmMergedTimelineResult":{"type":"object","required":["entities","timeline","has_more","unavailable_crm_entity_ids"],"properties":{"entities":{"type":"array","description":"The records whose timelines were merged (the ids the caller could access).","items":{"$ref":"#/components/schemas/CrmTimelineEntity"}},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/CrmMergedTimelineEntry"}},"has_more":{"type":"boolean","description":"True when older entries were trimmed past the global cap."},"unavailable_crm_entity_ids":{"type":"array","description":"Requested ids that were not found or not permitted, and so excluded.","items":{"type":"integer","format":"int64"}}}},"CrmTimelineEntity":{"type":"object","description":"The record a timeline (or timeline entry) belongs to.","required":["crm_entity_id","entity_kind","full_name","event_name"],"properties":{"crm_entity_id":{"type":"integer","format":"int64"},"entity_kind":{"type":"string"},"full_name":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true}}},"CrmMergedTimelineEntry":{"description":"A CrmTimelineEntry tagged with the record it came from, for merged feeds.","allOf":[{"$ref":"#/components/schemas/CrmTimelineEntry"},{"type":"object","required":["crm_entity_id","event_name"],"properties":{"crm_entity_id":{"type":"integer","format":"int64","description":"The record this entry belongs to."},"event_name":{"type":"string","nullable":true,"description":"The event of the record this entry belongs to."}}}]},"CrmTimelineEntry":{"type":"object","required":["occurred_at","action","actor","details"],"properties":{"occurred_at":{"type":"string","format":"date-time"},"action":{"type":"string","description":"What happened, e.g. \"registered\", \"edited 2 fields\", \"transferred bib\", \"opened\"."},"actor":{"type":"string","nullable":true,"description":"Who made the change (\"System\" for automated changes)."},"details":{"type":"array","description":"Field-level detail lines, e.g. \"Email: old@x.com → new@x.com\".","items":{"type":"string"}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/timelines":{"get":{"tags":["CRM"],"summary":"List a merged activity timeline across many records","description":"Merges the activity history of several CRM records into one feed,\nnewest-first — for answering \"what's the history of <person>?\" in a\nsingle call when a person spans multiple records (one per\nevent/record-kind). Each entry is tagged with its `crm_entity_id` and\n`event_name`, and the `entities` block maps every id to its\nname/kind/event. `has_more` is true when older entries were trimmed past\nthe global cap. Ids the caller can't access are dropped and returned in\n`unavailable_crm_entity_ids`.\n","operationId":"listCrmTimelines","parameters":[{"name":"crm_entity_ids","in":"query","required":true,"description":"The record ids to merge (from searchCrmEntities), comma-separated (e.g. `12,34,56`). Up to 100.","style":"form","explode":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"limit","in":"query","required":false,"description":"Maximum entries to return across all records, newest first. Defaults\nto 50; values above 200 are clamped to 200.\n","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"The merged activity timeline, newest first (may be empty)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmMergedTimelineResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"crm_entity_ids is missing or contains no valid positive ids","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## List available CRM columns and record types

> The catalog a CRM mode is built from: every static column's \`key\` (the\
> value stored in a mode's column list) and human \`label\`, plus the\
> \`record\_types\` a mode can be scoped to. Gated on the caller having CRM\
> access; no per-record authorization.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"schemas":{"CrmColumnCatalog":{"type":"object","required":["columns","default_columns","record_types"],"properties":{"columns":{"type":"array","items":{"$ref":"#/components/schemas/CrmColumn"}},"default_columns":{"type":"array","description":"The standard starting column keys for a new mode.","items":{"type":"string"}},"record_types":{"type":"array","description":"The record types a mode can be scoped to (humanized).","items":{"type":"string"}}}},"CrmColumn":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","description":"The column key used in a mode's column list."},"label":{"type":"string","description":"Human-facing column label."}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/columns":{"get":{"tags":["CRM"],"summary":"List available CRM columns and record types","description":"The catalog a CRM mode is built from: every static column's `key` (the\nvalue stored in a mode's column list) and human `label`, plus the\n`record_types` a mode can be scoped to. Gated on the caller having CRM\naccess; no per-record authorization.\n","operationId":"listCrmColumns","responses":{"200":{"description":"The column and record-type catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmColumnCatalog"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
````

## List the caller's CRM modes

> Returns the organizer's CRM modes (tabs/views): built-in templates and\
> their own custom modes. Only custom modes carry a non-null \`id\` and can\
> be updated or deleted. Built-in editable modes report an empty \`columns\`\
> list because their columns are managed live in the UI, not persisted.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"schemas":{"CrmModeListResult":{"type":"object","required":["modes"],"properties":{"modes":{"type":"array","items":{"$ref":"#/components/schemas/CrmMode"}}}},"CrmMode":{"type":"object","required":["id","key","name","template","custom","hidden","columns","record_types"],"properties":{"id":{"type":"integer","format":"int64","nullable":true,"description":"The saved-mode id; null for built-in template modes."},"key":{"type":"string"},"name":{"type":"string"},"template":{"type":"string"},"custom":{"type":"boolean","description":"Whether this is a user-created mode (the ones this surface can update/delete)."},"hidden":{"type":"boolean"},"columns":{"type":"array","description":"Ordered column keys shown by the mode.","items":{"type":"string"}},"record_types":{"type":"array","items":{"type":"string"}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/modes":{"get":{"tags":["CRM"],"summary":"List the caller's CRM modes","description":"Returns the organizer's CRM modes (tabs/views): built-in templates and\ntheir own custom modes. Only custom modes carry a non-null `id` and can\nbe updated or deleted. Built-in editable modes report an empty `columns`\nlist because their columns are managed live in the UI, not persisted.\n","operationId":"listCrmModes","responses":{"200":{"description":"The caller's modes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmModeListResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
````

## Create a custom CRM mode

> Creates a new custom saved view for the caller. The action-menu\
> (\`actions\`) column is prepended automatically when omitted. Column keys\
> must come from the columns catalog; unknown keys return 422.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"schemas":{"CreateCrmModeRequest":{"type":"object","required":["name","columns"],"properties":{"name":{"type":"string","description":"Organizer-facing name (unique among the caller's modes)."},"columns":{"type":"array","description":"Ordered column keys to show (from the columns catalog).","items":{"type":"string"}},"record_types":{"type":"array","description":"Record types to scope to; omit or empty for all records.","items":{"type":"string"}}}},"CrmModeResult":{"type":"object","required":["mode"],"properties":{"mode":{"$ref":"#/components/schemas/CrmMode"}}},"CrmMode":{"type":"object","required":["id","key","name","template","custom","hidden","columns","record_types"],"properties":{"id":{"type":"integer","format":"int64","nullable":true,"description":"The saved-mode id; null for built-in template modes."},"key":{"type":"string"},"name":{"type":"string"},"template":{"type":"string"},"custom":{"type":"boolean","description":"Whether this is a user-created mode (the ones this surface can update/delete)."},"hidden":{"type":"boolean"},"columns":{"type":"array","description":"Ordered column keys shown by the mode.","items":{"type":"string"}},"record_types":{"type":"array","items":{"type":"string"}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/modes":{"post":{"tags":["CRM"],"summary":"Create a custom CRM mode","description":"Creates a new custom saved view for the caller. The action-menu\n(`actions`) column is prepended automatically when omitted. Column keys\nmust come from the columns catalog; unknown keys return 422.\n","operationId":"createCrmMode","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCrmModeRequest"}}}},"responses":{"201":{"description":"Mode created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmModeResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Unknown column key, duplicate name, or invalid record type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Hide or show a CRM mode's tab

> Hides or shows a mode's tab for the caller (per-athlete). Addresses the\
> mode by its \`key\` from the modes list — a built-in key or a custom\
> \`saved\_\<id>\`. The \`all\_records\` mode is the default fallback and stays\
> visible even when hidden is set.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"schemas":{"SetCrmModeVisibilityRequest":{"type":"object","required":["mode_key","hidden"],"properties":{"mode_key":{"type":"string","description":"The mode's key (a built-in key or a custom \"saved_<id>\")."},"hidden":{"type":"boolean","description":"true to hide the tab, false to show it."}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/mode_visibility":{"patch":{"tags":["CRM"],"summary":"Hide or show a CRM mode's tab","description":"Hides or shows a mode's tab for the caller (per-athlete). Addresses the\nmode by its `key` from the modes list — a built-in key or a custom\n`saved_<id>`. The `all_records` mode is the default fallback and stays\nvisible even when hidden is set.\n","operationId":"setCrmModeVisibility","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCrmModeVisibilityRequest"}}}},"responses":{"200":{"description":"Visibility updated","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Mode not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Delete a custom CRM mode

> Deletes a custom mode by id. Built-in modes cannot be deleted (404). The\
> underlying records are untouched.<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"ResourceId":{"name":"id","in":"path","required":true,"description":"The ID of the resource","schema":{"type":"integer","format":"int64"}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}}},"paths":{"/api/v1/crm/modes/{id}":{"delete":{"tags":["CRM"],"summary":"Delete a custom CRM mode","description":"Deletes a custom mode by id. Built-in modes cannot be deleted (404). The\nunderlying records are untouched.\n","operationId":"deleteCrmMode","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"200":{"description":"Mode deleted","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Custom mode not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Update a custom CRM mode

> Updates a custom mode by id. Only the fields sent are changed; a sent\
> \`columns\` array REPLACES the mode's columns (it does not merge). Built-in\
> modes cannot be updated (404).<br>

````json
{"openapi":"3.1.0","info":{"title":"Movemint API","version":"1.0"},"tags":[{"name":"CRM","description":"Organizer-level CRM surface used by Movemint AI. Records of every kind\n(participants, external contacts, donations, volunteers, waitlisters,\nfinishers, merchandise orders) are addressed by their `crm_entity_id`,\nnot nested under an event. Access is per record: on the event the record\nbelongs to, the caller must both hold the CRM scope that governs that\nrecord's kind (`crm_participants`, `crm_donations`, …) **and** have the\nAI-assistant (`ai_chat`) capability enabled. A record on an event without\neither returns `403 entity_not_permitted`.\n\nRecord access is deliberately scoped to lookup and **private notes** only\n— there is no endpoint to edit or delete a record, and notes are\nappend-only (no update or delete). There is no bulk endpoint; each call\nacts on one record. The tag also covers the athlete-scoped **modes** and\n**columns** surface (saved CRM views): the columns catalog plus create /\nupdate / delete / hide-show of the caller's own custom modes.\n"}],"servers":[{"url":"https://www.movemint.cc","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Pass the access token in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_ACCESS_TOKEN\n```\n"},"oauth2":{"type":"oauth2","description":"OAuth 2.0 authentication using the Authorization Code or Client\nCredentials grant flow.\n","flows":{"authorizationCode":{"authorizationUrl":"https://www.movemint.cc/oauth/authorize","tokenUrl":"https://www.movemint.cc/oauth/token","refreshUrl":"https://www.movemint.cc/oauth/token","scopes":{}},"clientCredentials":{"tokenUrl":"https://www.movemint.cc/oauth/token","scopes":{}}}}},"parameters":{"ResourceId":{"name":"id","in":"path","required":true,"description":"The ID of the resource","schema":{"type":"integer","format":"int64"}}},"schemas":{"UpdateCrmModeRequest":{"type":"object","description":"Only the fields sent are changed.","properties":{"name":{"type":"string"},"columns":{"type":"array","description":"Full ordered column keys; REPLACES the mode's columns.","items":{"type":"string"}},"record_types":{"type":"array","description":"New record-type scope; [] for all records.","items":{"type":"string"}}}},"CrmModeResult":{"type":"object","required":["mode"],"properties":{"mode":{"$ref":"#/components/schemas/CrmMode"}}},"CrmMode":{"type":"object","required":["id","key","name","template","custom","hidden","columns","record_types"],"properties":{"id":{"type":"integer","format":"int64","nullable":true,"description":"The saved-mode id; null for built-in template modes."},"key":{"type":"string"},"name":{"type":"string"},"template":{"type":"string"},"custom":{"type":"boolean","description":"Whether this is a user-created mode (the ones this surface can update/delete)."},"hidden":{"type":"boolean"},"columns":{"type":"array","description":"Ordered column keys shown by the mode.","items":{"type":"string"}},"record_types":{"type":"array","items":{"type":"string"}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code"},"error_description":{"type":"string","description":"Human-readable description of the error"}}},"Error":{"type":"object","description":"A generic error response","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message"}}}},"responses":{"Unauthorized":{"description":"Unauthorized - invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"Forbidden":{"description":"Forbidden - user is not an admin of this event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/crm/modes/{id}":{"patch":{"tags":["CRM"],"summary":"Update a custom CRM mode","description":"Updates a custom mode by id. Only the fields sent are changed; a sent\n`columns` array REPLACES the mode's columns (it does not merge). Built-in\nmodes cannot be updated (404).\n","operationId":"updateCrmMode","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCrmModeRequest"}}}},"responses":{"200":{"description":"Mode updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmModeResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Custom mode not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unknown column key, duplicate name, or invalid record type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````
