{"openapi":"3.1.0","info":{"title":"ChoiceSpin developer API","version":"1.0.0","description":"Local candidate, not a deployed production service. Server-to-server API keys manage owned saved wheels. Unlisted share links expire after 24h; updating a saved wheel does not change an earlier share. API keys stay out of browsers. Pilot: 100 saved wheels and 100 active shares/developer; 60 management requests/minute, 20 mutations/minute, 1000 mutations/day across keys;global authenticated/read ceilings 300/minute and 10000/day. No GIF/WebP animation service, weights/images or multiple wheels in this version. Saved records persist until deleted; browser control never saves or updates them."},"servers":[{"url":"http://127.0.0.1:4187/api/v1","description":"Default local server"},{"url":"https://choicespin.com/api/v1","description":"Reserved; disabled until separately authorized and configured"}],"security":[{"developerKey":[]}],"paths":{"/health":{"get":{"summary":"Check the API process","security":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"},"requestId":{"type":"string"}}}}}}},"operationId":"getHealth"}},"/openapi.json":{"get":{"summary":"Download this specification","security":[],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"getOpenApi"}},"/wheels":{"get":{"summary":"List owned saved wheels","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string","maxLength":512},"description":"Opaque nextCursor from the preceding response; bound to this developer."}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"wheels":{"type":"array","items":{"$ref":"#/components/schemas/Wheel"}},"nextCursor":{"type":["string","null"]}}},"requestId":{"type":"string"}}}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"listWheels"},"post":{"summary":"Create a saved wheel and optionally a share link","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":128,"pattern":"^[A-Za-z0-9_-]+$"},"description":"Retain for every retry of this operation. Replays are guaranteed for 24 hours; changed requests return 409."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["config"],"properties":{"config":{"$ref":"#/components/schemas/Config"},"share":{"type":"boolean","default":false}},"example":{"config":{"title":"Next activity","entries":["Drawing","Music","Reading"]},"share":true}}}}},"responses":{"201":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WheelResponse"}}},"headers":{"ETag":{"schema":{"type":"string"}},"Location":{"schema":{"type":"string"}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"createWheel"}},"/wheels/{wheelId}":{"parameters":[{"name":"wheelId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{24}$"}}],"get":{"summary":"Read an owned saved wheel","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WheelResponse"}}},"headers":{"ETag":{"schema":{"type":"string"}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"getWheel"},"put":{"summary":"Replace configuration using its current ETag","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","example":"\"1\""},"description":"ETag returned by the latest wheel read or write. Missing:428, stale:412."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["config"],"properties":{"config":{"$ref":"#/components/schemas/Config"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WheelResponse"}}},"headers":{"ETag":{"schema":{"type":"string"}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"replaceWheel"},"delete":{"summary":"Delete a saved wheel and disable its API-created share links","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","example":"\"1\""},"description":"ETag returned by the latest wheel read or write. Missing:428, stale:412."}],"responses":{"204":{"description":"Deleted; empty response body"},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"deleteWheel"}},"/wheels/{wheelId}/shares":{"parameters":[{"name":"wheelId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{24}$"}}],"post":{"summary":"Share a new 24-hour snapshot of the current version","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":128,"pattern":"^[A-Za-z0-9_-]+$"},"description":"Retain for every retry of this operation. Replays are guaranteed for 24 hours; changed requests return 409."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","example":"\"1\""},"description":"ETag returned by the latest wheel read or write. Missing:428, stale:412."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false}}}},"responses":{"201":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareResponse"}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"createShare"}},"/wheels/{wheelId}/shares/{shareId}":{"parameters":[{"name":"wheelId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{24}$"}},{"name":"shareId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{24}$"}}],"delete":{"summary":"Disable an owned share link","responses":{"204":{"description":"Disabled; empty response body"},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"deleteShare"}},"/shares/read":{"post":{"summary":"Read a valid unlisted snapshot using its read capability","description":"Credentialless read, not key authentication. CORS permits any origin including sandbox null; credentials are never allowed. No management operation is available here.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["capability"],"properties":{"capability":{"type":"string","pattern":"^cs_share_[A-Za-z0-9_-]{24}\\.[A-Za-z0-9_-]{43}$"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"wheelId":{"type":"string"},"version":{"type":"integer"},"url":{"type":"string"},"embedUrl":{"type":"string"},"expiresAt":{"type":"string"},"config":{"$ref":"#/components/schemas/Config"}}},"requestId":{"type":"string"}}}}}},"400":{"description":"HTTP 400; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"HTTP 401; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"HTTP 403; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"HTTP 404; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"HTTP 409; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"HTTP 412; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTTP 413; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"HTTP 415; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"HTTP 428; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"HTTP 429; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"HTTP 503; inspect error.code and requestId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"operationId":"readShare"}},"/view/{capability}":{"get":{"summary":"Open or embed a playable unlisted snapshot","security":[],"parameters":[{"name":"capability","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sandboxed noindex/no-store HTML using the existing ChoiceSpin wheel","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Expired, revoked, deleted or invalid wheel link","content":{"text/html":{"schema":{"type":"string"}}}},"429":{"description":"Read limit reached; Retry-After header"},"503":{"description":"Temporarily unavailable"}},"operationId":"openSharedWheel"}}},"components":{"securitySchemes":{"developerKey":{"type":"http","scheme":"bearer","description":"A revocable scoped key issued by the operator. Never include it in a browser script, iframe or URL."}},"schemas":{"Config":{"type":"object","additionalProperties":false,"required":["entries"],"properties":{"title":{"type":"string","maxLength":80},"entries":{"type":"array","minItems":2,"maxItems":100,"items":{"type":"string","minLength":1,"maxLength":80}},"colors":{"type":"array","minItems":2,"maxItems":12,"items":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"}},"duration":{"type":"number","minimum":1,"maximum":15,"default":5},"appearance":{"type":"string","enum":["light","dark","transparent"],"default":"light"},"interaction":{"type":"string","enum":["spin","controls","edit"],"default":"spin"},"showResult":{"type":"boolean","default":true},"removeWinner":{"type":"boolean","default":false},"v":{"type":"integer","const":1},"mode":{"type":"string","const":"website"},"aspect":{"type":"string","const":"square"},"sound":{"type":"boolean","const":false},"branding":{"type":"boolean","const":true}},"description":"Single text wheel. Text must be valid Unicode without unsafe controls. The complete normalized encoded configuration must also fit 6000 base64url characters, so maximum field lengths cannot all be combined. Constant fields may be omitted; returned config can be sent back unchanged."},"Wheel":{"type":"object","required":["id","version","config","createdAt","updatedAt"],"properties":{"id":{"type":"string"},"version":{"type":"integer","minimum":1},"config":{"$ref":"#/components/schemas/Config"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Share":{"type":"object","required":["id","wheelId","version","url","embedUrl","expiresAt"],"properties":{"id":{"type":"string"},"wheelId":{"type":"string"},"version":{"type":"integer"},"url":{"type":"string","format":"uri"},"embedUrl":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error","requestId"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}},"requestId":{"type":"string","format":"uuid"}}},"WheelResponse":{"type":"object","required":["data","requestId"],"properties":{"data":{"type":"object","required":["wheel"],"properties":{"wheel":{"$ref":"#/components/schemas/Wheel"},"share":{"$ref":"#/components/schemas/Share"}}},"requestId":{"type":"string","format":"uuid"}}},"ShareResponse":{"type":"object","required":["data","requestId"],"properties":{"data":{"type":"object","required":["share"],"properties":{"share":{"$ref":"#/components/schemas/Share"}}},"requestId":{"type":"string","format":"uuid"}}}}}}