{"openapi":"3.1.0","info":{"title":"SpaceCare Public API","description":"## Introduction\n\nSpaceCare helps professionals package their expertise into structured content - courses built from ordered modules, plus podcasts, newsletters, and downloadable resources - and share it with the clients they work with.\n\nThe public API brings that into your own systems: an approved partner application can read a creator's published courses - so delivery and follow-up live in the tools your team already uses.\n\n### What you can do\n\n- **Read your catalogue from your own product.** List a creator's courses and read module headers to link out or embed.\n\n## Overview\n\nThe API is REST over HTTPS with JSON request and response bodies. Every request carries a bearer token, is gated by scope, and is owner-scoped - a token only ever reads or writes its own account's data.\n\nBase URL: `https://space.care/api/v1`\n\nSuccessful responses use the envelope `{ data, meta? }`; failures use `{ error: { code, message, details? } }`. Branch on the machine-stable `error.code`, never on the human-readable `error.message`.\n\n## Getting started\n\n1. **Mint a scoped API key.** Sign in to the SpaceCare app and open [Settings -> Developer API](https://space.care/settings?tab=developers) to create a key. Scopes are selectable when you mint the key - grant only what your integration needs.\n2. **Send the key as a bearer token.** Add an `Authorization` header to every request:\n\n```http\nAuthorization: Bearer <your-api-key>\n```\n\n3. **Verify your credential.** Call `GET https://space.care/api/v1/me` to confirm the token works and read back its granted scopes.\n\nA missing or invalid token returns `401 unauthorized`; a valid token missing a required scope returns `403 insufficient_scope`.\n\n## Scopes\n\nA credential carries one or more of these scopes. Operations declare the scope they require.\n\n- `read:profile` - verify a credential and read back its identity and scopes.\n- `read:courses` - list and read your own courses and module headers, never module bodies.\n\n## Machine-readable specification\n\nA public, unauthenticated OpenAPI 3.1 document is served at `https://docs.space.care/openapi.json` - a bare OpenAPI document (not wrapped in the `{ data }` envelope) so tooling can consume it directly. The spec only describes shapes and carries no secret; every call still requires a bearer token.\n\nA Markdown rendering of the full reference is at `https://docs.space.care/openapi.md`, and an `llms.txt` index for AI agents at `https://docs.space.care/llms.txt`.\n\nCredentialed tooling can also fetch the same document from the API host at `https://space.care/api/v1/openapi.json` (bearer-gated).","version":"1.0.0"},"servers":[{"url":"https://space.care/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"courses","description":"List and read the caller's own courses."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"additionalProperties":false,"description":"Failure envelope. Clients branch on `error.code` (machine-stable), never on `error.message` (human, may change wording).","properties":{"error":{"type":"object","required":["code","message"],"additionalProperties":false,"properties":{"code":{"type":"string","enum":["unauthorized","insufficient_scope","not_found","invalid_request","rate_limited","internal_error"],"description":"Machine-stable error code."},"message":{"type":"string","description":"Human-readable explanation (not stable)."},"details":{"description":"Optional structured detail; shape varies by code."}}}}},"PaginationMeta":{"type":"object","required":["nextCursor"],"additionalProperties":false,"description":"Pagination metadata. `nextCursor` is an opaque cursor to pass back as the `cursor` query param for the next page, or null when there is no further page.","properties":{"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null if none."}}},"CourseListItem":{"type":"object","required":["slug","title","language","publishedAt","hidden","moduleCount"],"additionalProperties":false,"description":"Safe public projection of a course in a list.","properties":{"slug":{"type":"string"},"title":{"type":"string"},"language":{"type":"string"},"publishedAt":{"type":["string","null"],"description":"ISO 8601 publish timestamp, or null if unpublished."},"hidden":{"type":"boolean"},"moduleCount":{"type":"integer"}}},"Module":{"type":"object","required":["number","title","summary"],"additionalProperties":false,"description":"Module header. The module body (`content`) is never exposed.","properties":{"number":{"type":"integer","description":"1-based index of the module within the course."},"title":{"type":"string"},"summary":{"type":"string"}}},"CourseDetail":{"type":"object","required":["slug","title","tagline","language","publishedAt","hidden","accessMode","modules"],"additionalProperties":false,"description":"Course detail with module headers (number/title/summary) but never module bodies.","properties":{"slug":{"type":"string"},"title":{"type":"string"},"tagline":{"type":"string"},"language":{"type":"string"},"publishedAt":{"type":["string","null"]},"hidden":{"type":"boolean"},"accessMode":{"type":["string","null"],"description":"Course access mode, or null if unset."},"modules":{"type":"array","items":{"$ref":"#/components/schemas/Module"}}}},"Identity":{"type":"object","required":["userId","apiKeyId","scopes"],"additionalProperties":false,"description":"The resolved identity behind the presented credential: the account id, the API key id, and the granted scopes.","properties":{"userId":{"type":"string"},"apiKeyId":{"type":"string","description":"`creator_api_keys.id` for an API key, or `oauth:<hash>` for an OAuth token."},"scopes":{"type":"array","items":{"type":"string"},"description":"Scopes granted to this credential."}}}},"responses":{"BadRequest":{"description":"Invalid request (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid bearer token (unauthorized).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Insufficient scope or unsupported credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found (not_found). Foreign ids 404 too.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"paths":{"/me":{"get":{"tags":["courses"],"summary":"Identity check","operationId":"getMe","description":"Verify a credential and read back the granted scopes. Requires only a valid credential (no specific scope). Returns the resolved identity: the account id, the API key id, and the granted scopes.","responses":{"200":{"description":"The resolved identity.","content":{"application/json":{"schema":{"type":"object","required":["data"],"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/Identity"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/courses":{"get":{"tags":["courses"],"summary":"List courses","operationId":"listCourses","description":"List the caller's own courses (newest first), omitting module bodies. Cursor-paginated. Requires scope: read:courses.","parameters":[{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a prior page's meta.nextCursor.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size (1-100, default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of courses.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"additionalProperties":false,"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CourseListItem"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/courses/{slug}":{"get":{"tags":["courses"],"summary":"Get course detail","operationId":"getCourse","description":"Course detail for the caller's own course (module headers, never bodies). A slug owned by someone else 404s. Requires scope: read:courses.","parameters":[{"name":"slug","in":"path","required":true,"description":"Course slug.","schema":{"type":"string"}}],"responses":{"200":{"description":"The course detail.","content":{"application/json":{"schema":{"type":"object","required":["data"],"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/CourseDetail"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}