openapi: 3.2.0 info: title: Savee Public Boards API version: 1.0.0 contact: name: Savee url: https://docs.savee.com email: hey@savee.com termsOfService: https://savee.com/terms/ license: name: Proprietary — Savee Terms of Service url: https://savee.com/terms/ description: 'Read-only REST API exposing a Savee user’s own saves, boards, and home feed, plus search over Savee’s public library. Authenticate with either a personal access token (`sv_live_…`) generated in your Savee settings, or an OAuth 2.1 access token (`sv_at_…`) obtained on one of your users’ behalf. OAuth tokens are limited to the scopes the user approved; personal tokens carry all of them. **Image format** — `media.thumbnail` and `media.original` for image saves are AVIF by default. Clients that cannot decode AVIF should send the request header `Avif-Fallback: 1` to receive JPG URLs instead. Video originals are always MP4.' servers: - url: https://api.savee.com tags: - name: Boards paths: /v1/boards: get: summary: List the authenticated user's boards description: 'Returns every board the caller can see as a tree in a single response (not paginated): personal boards they own, boards they collaborate on, and boards from any Savee Teams workspace they''re a member of. Only top-level boards appear at the root of `data`; a board''s children are nested under its `sub_boards` array (single-level, so each sub-board is a leaf). Use the `ownership_type` field on each board to tell user-owned boards apart from team boards. The order at every level follows the caller''s chosen boards-sort preference on savee.com.' tags: - Boards security: - BearerAuth: [] - OAuth2: - boards:read responses: '200': description: All boards visible to the caller, as a tree. content: application/json: schema: $ref: '#/components/schemas/BoardsList' '400': description: Invalid input. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid Bearer token. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '402': description: Authenticated but the user has no active subscription. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: The Public API is not available on this account. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. Retry after the number of seconds in `Retry-After`. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /v1/boards/{boardID}: get: summary: Get a single board description: '`boardID` is the board `id` returned by `/v1/boards`. Returns the board with its child boards nested under `sub_boards` — the same shape as a node in the `/v1/boards` tree — so there is no separate sub-boards endpoint. Accessible by any user with a role on the board (owner, admin, editor, viewer) and by team members for team-owned boards. Returns 404 when the board does not exist or the caller has no role on it — the API does not confirm whether someone else’s board exists.' tags: - Boards security: - BearerAuth: [] - OAuth2: - boards:read parameters: - schema: type: string description: The board id (the `id` returned by `/v1/boards`). required: true description: The board id (the `id` returned by `/v1/boards`). name: boardID in: path responses: '200': description: The board, with its sub-boards nested. content: application/json: schema: $ref: '#/components/schemas/BoardResponse' '400': description: Invalid input. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid Bearer token. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '402': description: Authenticated but the user has no active subscription. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: The Public API is not available on this account. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Board not found or not accessible to the caller. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. Retry after the number of seconds in `Retry-After`. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /v1/boards/{boardID}/saves: get: summary: List saves on a specific board description: '`boardID` is the board `id` returned by `/v1/boards`. Accessible by any user with a role on the board (owner, admin, editor, viewer) and by team members for team-owned boards. Returns 404 when the board does not exist or the caller has no role on it — the API does not confirm whether someone else’s board exists.' tags: - Boards security: - BearerAuth: [] - OAuth2: - boards:read - saves:read parameters: - schema: type: string description: The board id (the `id` returned by `/v1/boards`). required: true description: The board id (the `id` returned by `/v1/boards`). name: boardID in: path - schema: type: integer minimum: 1 maximum: 100 description: Page size (default 30, max 100). example: 30 required: false name: limit in: query - schema: type: string description: Opaque cursor returned in `next_cursor` from the previous page. Pass it through verbatim — the encoding is an implementation detail and may change. example: eyJjIjoiMjAyNi0wNS0wOFQxMjowMDowMFoiLCJpIjoiNjdhYWRhMjAifQ required: false name: cursor in: query responses: '200': description: A page of saves on the board. content: application/json: schema: $ref: '#/components/schemas/SavesPage' '400': description: Invalid input. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing or invalid Bearer token. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '402': description: Authenticated but the user has no active subscription. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: The Public API is not available on this account. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Board not found or not accessible to the caller. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded. Retry after the number of seconds in `Retry-After`. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Unexpected server error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' components: schemas: SubBoard: type: object properties: id: type: string description: Board identifier. Stable across renames and slug changes; safe to store as a foreign key on the caller’s side. name: type: string slug: type: string url: type: string format: uri description: Canonical page on savee.com for this board (uses the current slug). is_private: type: boolean saves_count: type: integer minimum: 0 created_at: type: string format: date-time og_image: type: - string - 'null' format: uri ownership_type: type: string enum: - team - user description: '`user` if the board belongs to a person (the caller, or someone who''s added the caller as a collaborator); `team` if it belongs to a Savee Teams workspace the caller is a member of.' sub_boards: type: array description: Always empty — sub-boards do not nest further (nesting is single-level). items: {} maxItems: 0 required: - id - name - slug - url - is_private - saves_count - created_at - og_image - ownership_type - sub_boards User: type: object properties: id: type: string description: User identifier. Stable across renames; safe to store as a foreign key on the caller’s side. example: 63e1a4c2d242ec00094007f1 username: type: string description: Current username. May change if the user renames. example: aliceb name: type: string example: Alice Bauer url: type: string format: uri example: https://savee.com/aliceb/ avatar_url: type: string format: uri example: https://dm.savee.com/user-avatar/original/8kQ2mZp.jpg required: - id - username - name - url - avatar_url description: The user who created this save. Omitted on /v1/saves (the caller is implicitly the author); present on /v1/feed and /v1/boards/{id}/saves where the author may differ from the caller. SavesPage: type: object properties: data: type: array items: $ref: '#/components/schemas/Save' next_cursor: type: - string - 'null' description: Pass as `cursor` to fetch the next page. `null` on the last page. example: eyJjIjoiMjAyNi0wNS0wOFQxMjowMDowMFoiLCJpIjoiNjdhYWRhMjAifQ has_more: type: boolean description: Whether another page is available. Keep paging while this is `true`. example: true required: - data - next_cursor - has_more BoardResponse: type: object properties: data: $ref: '#/components/schemas/Board' required: - data ErrorResponse: type: object properties: error: type: object properties: code: type: string message: type: string required: - code - message required: - error Board: type: object properties: id: type: string description: Board identifier. Stable across renames and slug changes; safe to store as a foreign key on the caller’s side. example: 69fbaf96d242ec48009df59a name: type: string example: Posters slug: type: string example: posters url: type: string format: uri description: Canonical page on savee.com for this board (uses the current slug). example: https://savee.com/aliceb/posters/ is_private: type: boolean example: false saves_count: type: integer minimum: 0 example: 128 created_at: type: string format: date-time example: '2026-04-01T09:00:00.000Z' og_image: type: - string - 'null' format: uri description: Share image for the board. `null` if one has not been generated yet. example: https://dm.savee.com/board-og/original/Lp9wQ2s.jpg ownership_type: type: string enum: - team - user description: '`user` if the board belongs to a person (the caller, or someone who''s added the caller as a collaborator); `team` if it belongs to a Savee Teams workspace the caller is a member of.' example: user sub_boards: type: array items: $ref: '#/components/schemas/SubBoard' description: Child boards nested one level under this board, ordered by the same boards-sort preference as the top level. required: - id - name - slug - url - is_private - saves_count - created_at - og_image - ownership_type - sub_boards Save: type: object properties: id: type: string description: Save identifier. Stable across renames; safe to store as a foreign key on the caller’s side. example: 67aada20d242ec0009400825 url: type: string format: uri description: Canonical page on savee.com for this save. example: https://savee.com/i/UXb_bvc/ name: type: string example: Brutalist poster source_url: type: - string - 'null' description: Where the save was originally taken from. `null` if unknown. example: https://example.com/poster created_at: type: string format: date-time example: '2026-05-08T12:00:00.000Z' is_private: type: boolean description: Whether the save itself is marked private. This is a property of the save, independent of whether it also sits in a private board. example: false total_saves: type: integer minimum: 0 description: How many users across Savee have saved this same asset. example: 42 media: $ref: '#/components/schemas/SaveMedia' colors: type: array items: $ref: '#/components/schemas/SaveColor' description: Dominant colors of the media, most prominent first. Empty when colors have not been extracted for this save (e.g. shortly after saving). user: $ref: '#/components/schemas/User' required: - id - url - name - source_url - created_at - is_private - total_saves - media - colors SaveMedia: type: object properties: type: type: string enum: - image - video example: image width: type: integer minimum: 0 example: 1600 height: type: integer minimum: 0 example: 2400 thumbnail: type: string format: uri description: 'Grid-sized preview (~420px wide). AVIF by default; clients that cannot decode AVIF should send `Avif-Fallback: 1` to receive JPG. Applies to both image and video assets (videos return a JPG/AVIF poster frame).' example: https://dm.savee.com/asset_image/w420/6r4nDqE.avif original: type: string format: uri description: 'Full-quality asset. Image: AVIF by default (JPG with `Avif-Fallback: 1`). Video: MP4.' example: https://dm.savee.com/asset_image/original/6r4nDqE.avif required: - type - width - height - thumbnail - original BoardsList: type: object properties: data: type: array items: $ref: '#/components/schemas/Board' required: - data SaveColor: type: object properties: color: type: string pattern: ^#[0-9A-F]{6}$ description: Hex color code, e.g. `#1A2B3C`. example: '#1A1A1A' amount: type: number minimum: 0 maximum: 1 description: Fraction of the media covered by this color (0–1). example: 0.62 required: - color - amount securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: sv_live_… description: '**Personal access token** (`sv_live_…`) — represents you and carries every scope, so no scope is required for this call. Best for your own scripts and internal tools. Generate one at https://savee.com/developers/.' OAuth2: type: oauth2 description: '**OAuth access token** (`sv_at_…`) — obtained on one of your users’ behalf and limited to the scopes they approved. Use this when you’re building a product other people sign into with Savee. See https://docs.savee.com/api/oauth. Missing the scope below returns `403` with a `WWW-Authenticate: Bearer error="insufficient_scope"` header naming it.' flows: authorizationCode: authorizationUrl: https://savee.com/oauth/authorize/ tokenUrl: https://savee.com/api/oauth/token/ refreshUrl: https://savee.com/api/oauth/token/ scopes: profile:read: Read the user’s username, name, and avatar saves:read: Read the user’s saves and home feed boards:read: Read the user’s boards and the saves on them search:read: Search Savee’s public library on the user’s behalf