# SpaceCare Public API

Version `1.0.0`.

## Introduction

SpaceCare 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.

The 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.

### What you can do

- **Read your catalogue from your own product.** List a creator's courses and read module headers to link out or embed.

## Overview

The 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.

Base URL: `https://space.care/api/v1`

Successful 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`.

## Getting started

1. **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.
2. **Send the key as a bearer token.** Add an `Authorization` header to every request:

```http
Authorization: Bearer <your-api-key>
```

3. **Verify your credential.** Call `GET https://space.care/api/v1/me` to confirm the token works and read back its granted scopes.

A missing or invalid token returns `401 unauthorized`; a valid token missing a required scope returns `403 insufficient_scope`.

## Scopes

A credential carries one or more of these scopes. Operations declare the scope they require.

- `read:profile` - verify a credential and read back its identity and scopes.
- `read:courses` - list and read your own courses and module headers, never module bodies.

## Machine-readable specification

A 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.

A 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`.

Credentialed tooling can also fetch the same document from the API host at `https://space.care/api/v1/openapi.json` (bearer-gated).

---

## Endpoints

Base URL: `https://space.care/api/v1`

### GET /me

**Identity check**

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**

| Status | Description |
| --- | --- |
| 200 | The resolved identity. |
| 401 | Missing or invalid bearer token (unauthorized). |
| 500 | Unexpected server error (internal_error). |

### GET /courses

**List courses**

List the caller's own courses (newest first), omitting module bodies. Cursor-paginated. Requires scope: read:courses.

**Parameters**

| Name | In | Required | Description |
| --- | --- | --- | --- |
| `cursor` | query | no | Opaque cursor from a prior page's meta.nextCursor. |
| `limit` | query | no | Page size (1-100, default 50). |

**Responses**

| Status | Description |
| --- | --- |
| 200 | A page of courses. |
| 401 | Missing or invalid bearer token (unauthorized). |
| 403 | Insufficient scope or unsupported credential. |
| 500 | Unexpected server error (internal_error). |

### GET /courses/{slug}

**Get course detail**

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 | In | Required | Description |
| --- | --- | --- | --- |
| `slug` | path | yes | Course slug. |

**Responses**

| Status | Description |
| --- | --- |
| 200 | The course detail. |
| 401 | Missing or invalid bearer token (unauthorized). |
| 403 | Insufficient scope or unsupported credential. |
| 404 | Resource not found (not_found). Foreign ids 404 too. |
| 500 | Unexpected server error (internal_error). |

---

## Schemas

### ErrorEnvelope

Failure envelope. Clients branch on `error.code` (machine-stable), never on `error.message` (human, may change wording).

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | object | yes |  |

### PaginationMeta

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.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `nextCursor` | string \| null | yes | Opaque cursor for the next page, or null if none. |

### CourseListItem

Safe public projection of a course in a list.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | string | yes |  |
| `title` | string | yes |  |
| `language` | string | yes |  |
| `publishedAt` | string \| null | yes | ISO 8601 publish timestamp, or null if unpublished. |
| `hidden` | boolean | yes |  |
| `moduleCount` | integer | yes |  |

### Module

Module header. The module body (`content`) is never exposed.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `number` | integer | yes | 1-based index of the module within the course. |
| `title` | string | yes |  |
| `summary` | string | yes |  |

### CourseDetail

Course detail with module headers (number/title/summary) but never module bodies.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | string | yes |  |
| `title` | string | yes |  |
| `tagline` | string | yes |  |
| `language` | string | yes |  |
| `publishedAt` | string \| null | yes |  |
| `hidden` | boolean | yes |  |
| `accessMode` | string \| null | yes | Course access mode, or null if unset. |
| `modules` | array of [Module](#module) | yes |  |

### Identity

The resolved identity behind the presented credential: the account id, the API key id, and the granted scopes.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `userId` | string | yes |  |
| `apiKeyId` | string | yes | `creator_api_keys.id` for an API key, or `oauth:<hash>` for an OAuth token. |
| `scopes` | array of string | yes | Scopes granted to this credential. |
