{"openapi":"3.0.0","info":{"title":"Citron public API","description":"The public surfaces of the Citron API: content delivery (REST and GraphQL), contact, forms and booking submits. Generated offline from citron-admin-api controller metadata and filtered to public routes. Console endpoints are not part of it.","version":"1.0","contact":{}},"servers":[{"url":"https://api.console.citronos.com"}],"tags":[{"name":"Content delivery"},{"name":"Contacts"},{"name":"Appointments"},{"name":"Forms (public)"},{"name":"GraphQL"}],"paths":{"/graphql":{"get":{"tags":["GraphQL"],"summary":"Run a query (GET, CDN-cacheable)","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"query","in":"query","required":false,"description":"The GraphQL document (GET).","schema":{"type":"string"}},{"name":"variables","in":"query","required":false,"description":"JSON-encoded variables (GET).","schema":{"type":"string"}},{"name":"operationName","in":"query","required":false,"description":"Operation to run (GET).","schema":{"type":"string"}},{"name":"extensions","in":"query","required":false,"description":"JSON-encoded extensions (GET), e.g. {\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"…\"}}.","schema":{"type":"string"}}],"responses":{"200":{"description":"GraphQL response ({ data, errors? }). PERSISTED_QUERY_NOT_FOUND arrives as 200 with errors."},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"description":"PERSISTED_QUERY_HASH_MISMATCH, or variables/extensions that are not JSON."},"401":{"description":"Missing, invalid or revoked API key."},"404":{"description":"The graphql module is off for this workspace (or its plan does not include it)."},"429":{"description":"Per-key rate limit (shared with REST)."}}},"post":{"tags":["GraphQL"],"summary":"Run a query (POST)","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"variables":{"type":"object","additionalProperties":true},"operationName":{"type":"string"},"extensions":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"GraphQL response ({ data, errors? }). PERSISTED_QUERY_NOT_FOUND arrives as 200 with errors."},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"description":"PERSISTED_QUERY_HASH_MISMATCH, or variables/extensions that are not JSON."},"401":{"description":"Missing, invalid or revoked API key."},"404":{"description":"The graphql module is off for this workspace (or its plan does not include it)."},"429":{"description":"Per-key rate limit (shared with REST)."}}}},"/v1/appointment-types":{"get":{"operationId":"AppointmentsController_getTypes[0]","parameters":[],"responses":{"200":{"description":""}},"tags":["Appointments"]}},"/v1/appointments":{"post":{"operationId":"AppointmentsController_book[0]","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string (max 200 characters). A retry with the same key returns the first result.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookAppointmentDto"}}}},"responses":{"201":{"description":""}},"summary":"Book an appointment (public, idempotent, server-validated slot).","tags":["Appointments"]}},"/v1/appointments/slots":{"get":{"operationId":"AppointmentsController_getSlots[0]","parameters":[{"name":"typeId","required":true,"in":"query","schema":{"type":"string"},"description":"Appointment type id (from appointment-types)."},{"name":"staffId","required":false,"in":"query","schema":{"type":"string"},"description":"Only this staff member."},{"name":"locationId","required":false,"in":"query","schema":{"type":"string"},"description":"Only this location."},{"name":"mode","required":false,"in":"query","schema":{"type":"string"},"description":"online or in_office."},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"ISO timestamp. Default: now."},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"ISO timestamp. Default: 30 days from now."}],"responses":{"200":{"description":""}},"summary":"Available booking slots for a type/staff/mode (public).","tags":["Appointments"]}},"/v1/contacts":{"post":{"operationId":"ContactsController_submit[0]","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string (max 200 characters). A retry with the same key returns the first result.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitContactDto"}}}},"responses":{"201":{"description":""}},"summary":"Submit a contact form message (public, rate-limited).","tags":["Contacts"]}},"/v1/content/{collection}":{"get":{"operationId":"DeliveryController_list","parameters":[{"name":"collection","required":true,"in":"path","schema":{"type":"string"}},{"name":"locale","in":"query","required":false,"description":"Flatten localized fields to this locale (default-locale fallback). 400 for a locale the workspace does not have.","schema":{"type":"string"}},{"name":"filter","in":"query","required":false,"description":"`filter[field]=value` (eq) or `filter[field][op]=value`. Ops: eq ne lt lte gt gte contains in notIn. At most 10.","schema":{"type":"object","additionalProperties":true},"style":"deepObject","explode":true},{"name":"sort","in":"query","required":false,"description":"`field` ascending or `-field` descending. System fields: publishedAt createdAt updatedAt sortOrder slug.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"1 to 100. Default: the collection's own list size.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"populate","in":"query","required":false,"description":"Comma-separated relation fields to resolve into entries (max 10).","schema":{"type":"string"}},{"name":"fields","in":"query","required":false,"description":"Comma-separated data keys to return. System keys always come.","schema":{"type":"string"}},{"name":"richtext","in":"query","required":false,"description":"Richtext format.","schema":{"type":"string","enum":["html","json","both"],"default":"both"}},{"name":"status","in":"query","required":false,"description":"draft reads working copies; needs a ck_prev_ key.","schema":{"type":"string","enum":["draft"]}},{"name":"preview","in":"query","required":false,"description":"A signed preview token (cpt1.…); same as the x-citron-preview header.","schema":{"type":"string"}},{"name":"x-citron-preview","in":"header","required":false,"description":"A signed preview token from the console (cpt1.…). Serves drafts of the one collection it was issued for.","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag of a previous response; a match answers 304.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK. Cache-Control public, max-age=60, stale-while-revalidate=300 (no-store for drafts); ETag."},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"description":"Unknown filter field or operator, unknown locale, bad richtext value, too many filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"status=draft with a ck_pub_ key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown collection, or no such published entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit (default 300 requests per minute).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Content delivery"],"summary":"List entries (or the document of a singleton collection)","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}]}},"/v1/content/{collection}/{slugOrId}":{"get":{"operationId":"DeliveryController_get","parameters":[{"name":"collection","required":true,"in":"path","schema":{"type":"string"}},{"name":"slugOrId","required":true,"in":"path","schema":{"type":"string"}},{"name":"locale","in":"query","required":false,"description":"Flatten localized fields to this locale (default-locale fallback). 400 for a locale the workspace does not have.","schema":{"type":"string"}},{"name":"populate","in":"query","required":false,"description":"Comma-separated relation fields to resolve into entries (max 10).","schema":{"type":"string"}},{"name":"fields","in":"query","required":false,"description":"Comma-separated data keys to return. System keys always come.","schema":{"type":"string"}},{"name":"richtext","in":"query","required":false,"description":"Richtext format.","schema":{"type":"string","enum":["html","json","both"],"default":"both"}},{"name":"status","in":"query","required":false,"description":"draft reads working copies; needs a ck_prev_ key.","schema":{"type":"string","enum":["draft"]}},{"name":"preview","in":"query","required":false,"description":"A signed preview token (cpt1.…); same as the x-citron-preview header.","schema":{"type":"string"}},{"name":"x-citron-preview","in":"header","required":false,"description":"A signed preview token from the console (cpt1.…). Serves drafts of the one collection it was issued for.","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","required":false,"description":"ETag of a previous response; a match answers 304.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK. Cache-Control public, max-age=60, stale-while-revalidate=300 (no-store for drafts); ETag."},"304":{"description":"Not modified (If-None-Match matched the ETag)."},"400":{"description":"Unknown filter field or operator, unknown locale, bad richtext value, too many filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"status=draft with a ck_pub_ key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown collection, or no such published entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit (default 300 requests per minute).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Content delivery"],"summary":"One entry by slug or id","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}]}},"/v1/forms/{key}":{"get":{"operationId":"FormsPublicController_definition","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { key, name, fields, successMessage, redirectUrl, honeypotField, renderTokenField, renderToken, turnstileSiteKey } }"}},"summary":"Form definition for rendering, with a signed render token.","tags":["Forms (public)"]}},"/v1/forms/{key}/submissions":{"post":{"operationId":"FormsPublicController_submit","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string (max 200 characters). A retry with the same key returns the first result.","schema":{"type":"string"}}],"responses":{"201":{"description":"JSON submit: { data: { id, status: \"accepted\" }, warnings }."},"303":{"description":"Plain HTML post: redirect to the form's redirect URL or its thank-you page."}},"summary":"Submit a form. JSON `{ data, renderToken, turnstileToken? }` → 201; plain HTML form posts → 303 to the redirect URL or thank-you page.","tags":["Forms (public)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","additionalProperties":true,"description":"Field key → value, as in the form definition."},"renderToken":{"type":"string","description":"renderToken from GET /v1/forms/{key}, taken when the form was shown."},"turnstileToken":{"type":"string","description":"Required when the form has Turnstile on."},"referrer":{"type":"string"},"utm":{"type":"object","additionalProperties":{"type":"string"},"description":"utm_* keys only; others are dropped."}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","additionalProperties":true,"description":"Plain HTML post: the fields, plus _rt (render token), _hp (honeypot) and cf-turnstile-response. Answers 303."}}}}}},"/v1/public/{orgSlug}/appointment-types":{"get":{"operationId":"AppointmentsController_getTypes[1]","parameters":[{"name":"orgSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Appointments"]}},"/v1/public/{orgSlug}/appointments":{"post":{"operationId":"AppointmentsController_book[1]","parameters":[{"name":"orgSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string (max 200 characters). A retry with the same key returns the first result.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookAppointmentDto"}}}},"responses":{"201":{"description":""}},"summary":"Book an appointment (public, idempotent, server-validated slot).","tags":["Appointments"]}},"/v1/public/{orgSlug}/appointments/slots":{"get":{"operationId":"AppointmentsController_getSlots[1]","parameters":[{"name":"typeId","required":true,"in":"query","schema":{"type":"string"},"description":"Appointment type id (from appointment-types)."},{"name":"orgSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"staffId","required":false,"in":"query","schema":{"type":"string"},"description":"Only this staff member."},{"name":"locationId","required":false,"in":"query","schema":{"type":"string"},"description":"Only this location."},{"name":"mode","required":false,"in":"query","schema":{"type":"string"},"description":"online or in_office."},{"name":"from","required":false,"in":"query","schema":{"type":"string"},"description":"ISO timestamp. Default: now."},{"name":"to","required":false,"in":"query","schema":{"type":"string"},"description":"ISO timestamp. Default: 30 days from now."}],"responses":{"200":{"description":""}},"summary":"Available booking slots for a type/staff/mode (public).","tags":["Appointments"]}},"/v1/public/{orgSlug}/contacts":{"post":{"operationId":"ContactsController_submit[1]","parameters":[{"name":"orgSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string (max 200 characters). A retry with the same key returns the first result.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitContactDto"}}}},"responses":{"201":{"description":""}},"summary":"Submit a contact form message (public, rate-limited).","tags":["Contacts"]}}},"components":{"securitySchemes":{"apiKeyBearer":{"type":"http","scheme":"bearer","description":"A delivery key: ck_pub_… (published) or ck_prev_… (published and drafts)."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"BookAppointmentDto":{"type":"object","additionalProperties":false,"required":["contact","appointmentTypeId","mode","startsAt"],"properties":{"contact":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"maxLength":120,"minLength":2,"type":"string"},"email":{"type":"string","format":"email"},"phone":{"maxLength":40,"minLength":6,"type":"string"}}},"appointmentTypeId":{"type":"string"},"staffUserId":{"type":"string"},"mode":{"type":"string","enum":["online","in_office"]},"locationId":{"type":"string"},"startsAt":{"type":"string","format":"date-time"},"note":{"maxLength":1000,"type":"string"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"details":{},"requestId":{"type":"string"}}}}},"SubmitContactDto":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"subject":{"type":"string"},"message":{"type":"string"},"turnstileToken":{"type":"string"}}}}}}