# API Reference

HTTP API reference for YotoShelf — 153 endpoints across 14 groups. Generated from the OpenAPI spec produced by huma.

Source: https://yotoshelf.dev/openapi.yaml

## Authentication

Auth model (derived — not declared in spec):

- `public` — no session required (login, OAuth callbacks, health checks)
- `session` — active session cookie required
- `admin` — session required with admin role, or token auth (metrics)

## Groups

- **admin**: User management, audit log, job control, and system operations. Requires admin role.
- **auth**: Login, logout, OIDC flows, password reset, and email verification.
- **cards**: Create, edit, publish, and delete MYO audio cards.
- **collections**: Organise cards into shared collections with role-based access.
- **devices**: Linked Yoto player devices.
- **icons**: AI-generated card icon management and regeneration.
- **import**: Import cards from a linked Yoto account.
- **jobs**: Background job queue status.
- **labels**: Printable card label generation and sharing.
- **publish**: Publish cards to linked Yoto accounts.
- **recording**: Audio recording and track management.
- **settings**: LLM provider and application settings. Requires admin role.
- **tts**: Text-to-speech audio generation.
- **yoto**: Yoto account linking and OAuth callbacks.

## Admin

User management, audit log, job control, and system operations. Requires admin role.

### `GET /admin/audit-log` [admin]

View audit log

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `user_id` | query | `string` |  |  |
| `action` | query | `string` |  |  |
| `limit` | query | `integer` |  |  |
| `offset` | query | `integer` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /admin/deletion-requests` [admin]

List pending deletion requests

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /admin/deletion-requests/{id}/approve` [admin]

Approve a deletion request

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (ApproveDeletionInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `reassignments` | `object` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/deletion-requests/{id}/reject` [admin]

Reject a deletion request

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /admin/health` [admin]

Admin comprehensive health report

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | AdminHealthOutputBody |

### `GET /admin/jobs` [admin]

List all jobs with optional status/type filters

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `status` | query | `string` |  |  |
| `type` | query | `string` |  |  |
| `limit` | query | `integer` |  |  |
| `offset` | query | `integer` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `DELETE /admin/jobs/{id}` [admin]

Delete a job by ID

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /admin/orphaned-collections` [admin]

List orphaned collections

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /admin/orphaned-collections/{id}/assign` [admin]

Assign an orphaned collection to a user

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (AssignOrphanedInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `user_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/rotate-key` [admin]

Rotate the encryption key for stored tokens

**Request body (RotateKeyInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `old_key` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MessageOutputBody |

### `POST /admin/seed-examples` [admin]

Seed example cards for first-run experience

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | SeedExampleOutputBody |

### `GET /admin/seed-status` [admin]

Check whether example content has been seeded

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | SeedStatusOutputBody |

### `GET /admin/users` [admin]

List all users

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /admin/users` [admin]

Create a new user

**Request body (AdminCreateUserInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `display_name` | `string` |  |  |
| `email` | `string (email)` | yes |  |
| `password` | `string` | yes |  |
| `role` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | AdminUserOutputBody |

### `DELETE /admin/users/{id}` [admin]

Directly delete a user

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (AdminDeleteUserInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `reassignments` | `object` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /admin/users/{id}` [admin]

Update user role or display name

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (AdminUpdateUserInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `display_name` | `string` | yes |  |
| `role` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | AdminUserOutputBody |

### `POST /admin/users/{id}/deactivate` [admin]

Deactivate a user account

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/users/{id}/reactivate` [admin]

Reactivate a deactivated user

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/users/{id}/resend-verification` [admin]

Resend email verification to a user

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MessageOutputBody |

### `POST /admin/users/{id}/reset-password` [admin]

Admin-initiated password reset

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | AdminResetPasswordOutputBody |

### `POST /admin/users/{id}/revoke-sessions` [admin]

Revoke all sessions for a user

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/users/{id}/unlock` [admin]

Unlock a locked user account

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/users/{id}/verify-email` [admin]

Manually verify a user's email

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /health` [public]

Public health check

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | HealthOutputBody |

### `POST /library/rescan` [session]

Trigger a full library rescan

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | MessageOutputBody |

### `GET /metrics` [admin]

Basic system metrics

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MetricsOutputBody |

### `GET /ready` [public]

Readiness check — pings database

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ReadyOutputBody |

## Auth

Login, logout, OIDC flows, password reset, and email verification.

### `POST /auth/delete-request` [session]

Request account deletion

**Request body (DeletionRequestInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `reason` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | DeletionRequestOutputBody |

### `POST /auth/forgot-password` [public]

Request a password reset email

**Request body (ForgotPasswordInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `email` | `string (email)` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ForgotPasswordOutputBody |

### `POST /auth/login` [public]

Login with email and password

**Request body (LoginInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `email` | `string (email)` | yes | User email |
| `password` | `string` | yes | User password |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | UserOut |

### `POST /auth/logout` [public]

Logout and clear session

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /auth/me` [session]

Get current authenticated user

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | UserOut |

### `POST /auth/oidc/backchannel-logout` [public]

OIDC backchannel logout (provider-initiated)

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /auth/oidc/callback` [public]

OIDC callback (public)

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /auth/oidc/login` [public]

Initiate OIDC login flow

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /auth/oidc/unlink` [session]

Unlink OIDC SSO from account

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /auth/password` [session]

Change password (requires current password)

**Request body (ChangePasswordInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `current_password` | `string` | yes |  |
| `new_password` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ChangePasswordOutputBody |

### `PATCH /auth/profile` [session]

Update display name

**Request body (UpdateProfileInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `display_name` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | UpdateProfileOutputBody |

### `POST /auth/reset-password` [public]

Reset password using a token

**Request body (ResetPasswordInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `new_password` | `string` | yes |  |
| `token` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ResetPasswordOutputBody |

### `POST /auth/sessions/revoke` [session]

Revoke all sessions (log out everywhere)

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /auth/verify-email` [public]

Verify email address using a token

**Request body (VerifyEmailInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `token` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | VerifyEmailOutputBody |

## Cards

Create, edit, publish, and delete MYO audio cards.

### `GET /cards` [session]

List all cards across all collections

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `DELETE /cards/{slug}` [session]

Delete a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /cards/{slug}` [session]

Get a card by slug with its tracks

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | CardDetailOut |

### `PUT /cards/{slug}` [session]

Update a card's title and description

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (UpdateCardInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `author` | `string` |  |  |
| `description` | `string` | yes |  |
| `title` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /cards/{slug}/cover` [session]

Serve card cover image

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `side` | query | `string` |  | Cover side |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /cards/{slug}/cover` [session]

Upload card cover image (multipart)

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `side` | query | `string` |  | Cover side |

**Request body:**

_No structured fields (see spec for raw schema)._

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/cover/accept-preview` [session]

Promote a generated cover preview to the final cover

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (AcceptCoverPreviewRequest):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `side` | `string` |  | Cover side |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GenerateCoverOutputBody |

### `POST /cards/{slug}/cover/generate` [session]

Generate a preview cover image (does not commit)

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (GenerateCoverRequest):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `prompt` | `string` | yes | Description of the desired cover image |
| `side` | `string` |  | Cover side |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GenerateCoverOutputBody |

### `GET /cards/{slug}/cover/preview` [session]

Serve the pending cover preview

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `side` | query | `string` |  | Cover side |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `PUT /cards/{slug}/home` [session]

Move a card to a different home collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (MoveCardHomeInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `collection_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/merge` [session]

Merge multiple tracks into one

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (MergeTracksInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `title` | `string` |  |  |
| `track_slugs` | `array | null` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | JobIDOutputBody |

### `GET /cards/{slug}/playback` [session]

Get playback configuration for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | PlaybackConfigOut |

### `PUT /cards/{slug}/playback` [session]

Update playback configuration for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (UpdatePlaybackConfigInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `autoadvance` | `string` | yes |  |
| `overlay_timeout` | `integer (int64)` | yes |  |
| `resume_timeout` | `integer (int64)` | yes |  |
| `shuffle` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /cards/{slug}/slug` [session]

Rename a card's slug

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (RenameSlugInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `slug` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /cards/{slug}/sources` [session]

List source material tracks for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `DELETE /cards/{slug}/sources/{track_slug}` [session]

Purge a source material track (deletes file and DB record)

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/tracks` [session]

Upload an audio track (multipart)

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body:**

_No structured fields (see spec for raw schema)._

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | TrackOut |

### `DELETE /cards/{slug}/tracks/{track_slug}` [session]

Delete a track from a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /cards/{slug}/tracks/{track_slug}` [session]

Get a track by slug

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | TrackDetailOut |

### `GET /cards/{slug}/tracks/{track_slug}/audio` [session]

Serve track audio file with Range support

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `PUT /cards/{slug}/tracks/{track_slug}/description` [session]

Update a track's description

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (UpdateTrackDescriptionInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `description` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/tracks/{track_slug}/fade` [session]

Apply fade in/out to a track

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (FadeTrackInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `fade_in_ms` | `integer (int64)` | yes |  |
| `fade_out_ms` | `integer (int64)` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | JobIDOutputBody |

### `PUT /cards/{slug}/tracks/{track_slug}/icon-prompt` [session]

Update a track's icon prompt

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (UpdateTrackIconPromptInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `icon_prompt` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /cards/{slug}/tracks/{track_slug}/slug` [session]

Rename a track's slug

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (RenameTrackSlugInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `slug` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/tracks/{track_slug}/split` [session]

Split a track at marker points

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (SplitTrackInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `split_points` | `array | null` | yes |  |
| `titles` | `array | null` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | JobIDOutputBody |

### `PUT /cards/{slug}/tracks/{track_slug}/title` [session]

Update a track's title and auto-update slug

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (UpdateTrackTitleInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `title` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | UpdateTrackTitleOutputBody |

### `POST /cards/{slug}/tracks/{track_slug}/trim` [session]

Trim a track to a selection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `track_slug` | path | `string` | yes |  |

**Request body (TrimTrackInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `end` | `number (double)` | yes |  |
| `start` | `number (double)` | yes |  |
| `title` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | JobIDOutputBody |

### `PUT /cards/{slug}/tracks/reorder` [session]

Reorder tracks on a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (ReorderTracksInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `positions` | `array | null` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /cards/{slug}/tracks/slug-available` [session]

Check if a track slug is available within a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `slug` | query | `string` | yes |  |
| `track_id` | query | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | TrackSlugAvailableOutputBody |

### `POST /cards/{slug}/tracks/stream` [session]

Add a streaming track to a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (AddStreamTrackInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `stream_url` | `string` | yes |  |
| `title` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | TrackOut |

### `POST /cards/{slug}/tracks/tts` [session]

Generate a TTS audio track from text

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (TTSGenerateInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `instructions` | `string` |  |  |
| `position` | `integer (int64)` |  |  |
| `text` | `string` | yes |  |
| `title` | `string` |  |  |
| `voice` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | TrackOut |

### `GET /cards/slug-available` [session]

Check if a card slug is available

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | query | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | SlugAvailableOutputBody |

### `GET /collections/{collectionID}/cards` [session]

List cards in a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `collectionID` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /collections/{collectionID}/cards` [session]

Create a new card in a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `collectionID` | path | `string` | yes |  |

**Request body (CreateCardInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `author` | `string` |  |  |
| `description` | `string` | yes |  |
| `slug` | `string` |  |  |
| `title` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | CardOut |

## Collections

Organise cards into shared collections with role-based access.

### `GET /collections` [session]

List collections for the authenticated user

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /collections` [session]

Create a new collection

**Request body (CreateCollectionInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `description` | `string` | yes |  |
| `name` | `string` | yes |  |
| `slug` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | CollectionOut |

### `DELETE /collections/{id}` [session]

Delete a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `cascade` | query | `boolean` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}` [session]

Get a collection by ID

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GetCollectionOutputBody |

### `PUT /collections/{id}` [session]

Update a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (UpdateCollectionInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `description` | `string` | yes |  |
| `name` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}/background` [session]

Serve collection background image

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /collections/{id}/background` [session]

Upload collection background image (multipart)

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body:**

_No structured fields (see spec for raw schema)._

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `DELETE /collections/{id}/cards/{cardId}/link` [session]

Unlink a card from a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `cardId` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /collections/{id}/cards/link` [session]

Link a card to a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (LinkCardInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `card_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}/members` [session]

List members of a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /collections/{id}/members` [session]

Add a member to a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (AddMemberInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `role` | `string` | yes |  |
| `user_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `DELETE /collections/{id}/members/{userId}` [session]

Remove a member from a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `userId` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /collections/{id}/members/{userId}` [session]

Update a member's role in a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `userId` | path | `string` | yes |  |

**Request body (UpdateMemberInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `role` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}/presets` [session]

List generation presets for a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /collections/{id}/presets` [session]

Create a generation preset

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (CreatePresetInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `duration_min` | `integer (int64)` | yes |  |
| `emoji` | `string` | yes |  |
| `name` | `string` | yes |  |
| `prompt_template` | `string` | yes |  |
| `sort_order` | `integer (int64)` | yes |  |
| `vocab_level` | `string` | yes |  |
| `voice_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | PresetOut |

### `DELETE /collections/{id}/presets/{preset_id}` [session]

Delete a generation preset

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `preset_id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /collections/{id}/presets/{preset_id}` [session]

Update a generation preset

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |
| `preset_id` | path | `string` | yes |  |

**Request body (UpdatePresetInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `duration_min` | `integer (int64)` | yes |  |
| `emoji` | `string` | yes |  |
| `name` | `string` | yes |  |
| `prompt_template` | `string` | yes |  |
| `sort_order` | `integer (int64)` | yes |  |
| `vocab_level` | `string` | yes |  |
| `voice_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}/profile` [session]

Get generation profile for a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | CollectionProfileOut |

### `PATCH /collections/{id}/profile` [session]

Update generation profile for a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (UpdateCollectionProfileInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `avoid_themes` | `string` | yes |  |
| `child_age` | `integer | null (int64)` | yes |  |
| `child_name` | `string` | yes |  |
| `duration_min` | `integer (int64)` | yes |  |
| `friends` | `string` | yes |  |
| `interests` | `string` | yes |  |
| `notes` | `string` | yes |  |
| `pets` | `string` | yes |  |
| `prompt_template` | `string` | yes |  |
| `vocab_level` | `string` | yes |  |
| `voice_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /collections/{id}/set-default` [session]

Set a collection as the default for Quick Record

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /collections/{id}/slug` [session]

Rename a collection's slug

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (RenameCollectionSlugInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `slug` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /collections/{id}/stats` [session]

Get stats for a collection

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | CollectionStatsOutputBody |

### `GET /collections/by-slug/{slug}` [session]

Get a collection by slug with its cards

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GetCollectionBySlugOutputBody |

### `POST /collections/default` [session]

Get or create the default collection for Quick Record

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | CollectionOut |

### `GET /collections/slug-available` [session]

Check if a collection slug is available

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | query | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | SlugAvailableOutputBody |

## Devices

Linked Yoto player devices.

### `GET /devices` [session]

List all devices across all linked Yoto accounts

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ListDevicesOutputBody |

### `GET /devices/{accountID}/{deviceID}/cache-status` [session]

Get device cache status from Yoto

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | CacheStatusOut |

### `POST /devices/{accountID}/{deviceID}/command` [session]

Send a command to a device

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Request body (SendCommandInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `card_id` | `string` |  |  |
| `command` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MessageOutputBody |

### `GET /devices/{accountID}/{deviceID}/info` [session]

Get full device info including status and settings

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | InfoSnapshot |

### `GET /devices/{accountID}/{deviceID}/published` [session]

List cards published to this device's account

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | PublishedCardsOutputBody |

### `PUT /devices/{accountID}/{deviceID}/settings` [session]

Update device settings

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Request body (UpdateSettingsInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `clock_face` | `string` |  |  |
| `day_brightness` | `string` |  |  |
| `day_max_volume` | `string` |  |  |
| `day_time` | `string` |  |  |
| `night_brightness` | `string` |  |  |
| `night_max_volume` | `string` |  |  |
| `night_time` | `string` |  |  |
| `shutdown_timeout_min` | `string` |  |  |
| `timezone` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MessageOutputBody |

### `PUT /devices/{accountID}/{deviceID}/shortcuts` [session]

Update device shortcut button assignments

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `accountID` | path | `string` | yes |  |
| `deviceID` | path | `string` | yes |  |

**Request body (UpdateShortcutsInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `shortcuts` | `array | null` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | MessageOutputBody |

### `POST /seed` [session]

Seed content to Yoto devices

**Request body (SeedContentInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `account_id` | `string` | yes |  |
| `card_ids` | `array | null` | yes |  |
| `collection_id` | `string` | yes |  |
| `device_ids` | `array | null` | yes |  |
| `schedule_at` | `string | null` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | SeedJobOut |

## Icons

AI-generated card icon management and regeneration.

### `GET /cards/{slug}/icons/{position}` [session]

Serve 16×16 track icon PNG

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /cards/{slug}/icons/{position}-full` [session]

Serve full-resolution track illustration

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /cards/{slug}/icons/{position}/preview/{index}` [session]

Serve an icon preview PNG

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |
| `index` | path | `integer` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /cards/{slug}/icons/{position}/regenerate` [session]

Regenerate a single icon for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GenerateIconsOutputBody |

### `POST /cards/{slug}/icons/{position}/select` [session]

Select an icon preview as the final icon

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Request body (SelectPreviewInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `index` | `integer (int64)` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/icons/{position}/variations` [session]

Generate N icon variations for preview

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Request body (GenerateVariationsInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `count` | `integer (int64)` | yes |  |
| `prompt` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | VariationsOutputBody |

### `POST /cards/{slug}/icons/generate` [session]

Batch generate icons for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GenerateIconsOutputBody |

### `POST /cards/{slug}/icons/sample` [session]

Generate icon samples for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | GenerateIconsOutputBody |

### `POST /cards/{slug}/icons/theme` [session]

Suggest an icon theme for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | SuggestThemeOutputBody |

### `PUT /cards/{slug}/icons/theme` [session]

Approve an icon theme for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (ApproveThemeInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `colors` | `array | null` | yes |  |
| `palette_name` | `string` | yes |  |
| `style` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /cards/{slug}/tracks/{position}/icon` [session]

Upload a custom icon for a track position

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |
| `position` | path | `integer` | yes |  |

**Request body:**

_No structured fields (see spec for raw schema)._

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

## Import

Import cards from a linked Yoto account.

### `POST /cards/import-from-yoto` [session]

Import a MYO card from a linked Yoto account (async)

**Request body (ImportFromYotoInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `account_id` | `string` | yes |  |
| `yoto_card_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted | ImportFromYotoOutputBody |

### `GET /yoto-accounts/{id}/library` [session]

List MYO cards from a linked Yoto account

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

## Jobs

Background job queue status.

### `GET /jobs/{id}` [session]

Get a single job by ID

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | JobOut |

## Labels

Printable card label generation and sharing.

### `GET /labels/{filename}` [session]

Download a generated label file

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `filename` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /labels/{id}/share` [session]

Create a share link for a label

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | LabelShareOut |

### `POST /labels/generate` [session]

Generate print-ready label PDF/SVG

**Request body (GenerateLabelsRequest):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `card_slugs` | `array | null` | yes |  |
| `format` | `string` | yes |  |
| `page_size` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | GenerateLabelsOutputBody |

### `DELETE /shares` [session]

Revoke all label share links for the authenticated user

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /shares` [session]

List label share links for the authenticated user

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `DELETE /shares/{id}` [session]

Revoke a label share link

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

## Publish

Publish cards to linked Yoto accounts.

### `GET /cards/{slug}/publish-status` [session]

Get publish status for a card

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /jobs` [session]

List background jobs

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /publish` [session]

Publish cards to Yoto accounts

**Request body (PublishCardsInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `account_ids` | `array | null` | yes |  |
| `card_slugs` | `array | null` | yes |  |
| `collection_id` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `202` | Accepted |  |

### `POST /publish/verify` [session]

Verify published cards against Yoto accounts

**Request body (VerifyPublishInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `account_ids` | `array | null` | yes |  |
| `card_slugs` | `array | null` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | VerifyPublishOutputBody |

## Recording

Audio recording and track management.

### `POST /cards/{slug}/tracks/record-start` [session]

Start a recording session with ffmpeg stdin pipe

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | `string` | yes |  |

**Request body (RecordStartInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `title` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | RecordStartOutputBody |

### `POST /recording/{session_id}/abandon` [session]

Abandon a recording session

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /recording/{session_id}/chunk` [session]

Write a chunk to the recording ffmpeg stdin pipe

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | `string` | yes |  |

**Request body:**

_No structured fields (see spec for raw schema)._

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /recording/{session_id}/finish` [session]

Finish recording: close pipe, transcode FLAC to M4A, create track

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `session_id` | path | `string` | yes |  |

**Request body (RecordFinishBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `title` | `string` |  |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | RecordFinishOutputBody |

## Settings

LLM provider and application settings. Requires admin role.

### `GET /admin/llm-providers` [admin]

List system LLM providers

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /admin/llm-providers` [admin]

Create a system LLM provider

**Request body (CreateProviderInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `api_key` | `string` | yes |  |
| `budget_usd` | `number (double)` | yes |  |
| `enabled` | `boolean` | yes |  |
| `model` | `string` | yes |  |
| `monthly_budget_cents` | `integer (int64)` |  |  |
| `priority` | `integer (int64)` | yes |  |
| `provider` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | LLMProviderOut |

### `DELETE /admin/llm-providers/{id}` [admin]

Delete a system LLM provider

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /admin/llm-providers/{id}` [admin]

Update a system LLM provider

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (UpdateProviderInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `api_key` | `string` |  |  |
| `budget_usd` | `number (double)` | yes |  |
| `enabled` | `boolean` | yes |  |
| `model` | `string` | yes |  |
| `monthly_budget_cents` | `integer (int64)` |  |  |
| `priority` | `integer (int64)` | yes |  |
| `provider` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /admin/llm-providers/test` [admin]

Test an LLM provider API key

**Request body (TestProviderInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `api_key` | `string` | yes |  |
| `provider` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | TestProviderOutputBody |

### `GET /settings/llm-providers` [session]

List user LLM providers

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `POST /settings/llm-providers` [session]

Create a user LLM provider

**Request body (CreateProviderInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `api_key` | `string` | yes |  |
| `budget_usd` | `number (double)` | yes |  |
| `enabled` | `boolean` | yes |  |
| `model` | `string` | yes |  |
| `monthly_budget_cents` | `integer (int64)` |  |  |
| `priority` | `integer (int64)` | yes |  |
| `provider` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `201` | Created | LLMProviderOut |

### `DELETE /settings/llm-providers/{id}` [session]

Delete a user LLM provider

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `PUT /settings/llm-providers/{id}` [session]

Update a user LLM provider

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Request body (UpdateProviderInputBody):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `api_key` | `string` |  |  |
| `budget_usd` | `number (double)` | yes |  |
| `enabled` | `boolean` | yes |  |
| `model` | `string` | yes |  |
| `monthly_budget_cents` | `integer (int64)` |  |  |
| `priority` | `integer (int64)` | yes |  |
| `provider` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `POST /settings/llm-providers/{id}/test` [session]

Test a user LLM provider API key

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | TestProviderOutputBody |

### `GET /settings/llm-providers/cascade` [session]

Get resolved LLM provider cascade for user

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

## Tts

Text-to-speech audio generation.

### `POST /tts/preview` [session]

Preview a TTS voice with a short text sample

**Request body (PreviewTTSVoiceRequest):**

| Field | Type | Req | Description |
| --- | --- | --- | --- |
| `instructions` | `string` |  |  |
| `text` | `string` | yes |  |
| `voice` | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

### `GET /tts/voices` [session]

List available TTS voices

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK |  |

## Yoto

Yoto account linking and OAuth callbacks.

### `GET /yoto-accounts` [session]

List linked Yoto accounts

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `200` | OK | ListYotoAccountsOutputBody |

### `DELETE /yoto-accounts/{id}` [session]

Unlink a Yoto account

**Parameters:**

| Name | In | Type | Req | Description |
| --- | --- | --- | --- | --- |
| `id` | path | `string` | yes |  |

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /yoto-accounts/callback` [public]

Yoto OAuth callback (public)

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |

### `GET /yoto-accounts/link` [session]

Initiate Yoto OAuth link flow

**Responses:**

| Code | Description | Schema |
| --- | --- | --- |
| `204` | No Content |  |
