This page is for software developers building against the Our Memory Book API. If you are here to run your own book, the help index has the guides for owners and contributors.
Per-endpoint quick reference for the integrator (API-key) routes on /api/. The contributor /me/... routes, cover upload, original-file download, transcription-on-request, device and push routes serve the Our Memory Book app and are not part of the integrator contract, so they are not listed here. For the long-form integration guide (auth, idempotency, error envelope, worked examples) see the API integration guide; for a copy-pasteable first call see API getting started; for change history see the API changelog.
Status: pre-release. Every route on this page is stable and safe to build on; removals follow the deprecation policy in the conventions below. Need a route that is not here? Get in touch.
Conventions
Every endpoint on /api/ shares these contracts, and the summary tables assume them:
- Who can call it. The minimum role per route. Owner or admin means a membership with that role on the book; contributor-role members have no general API access. Any signed-in user means any authenticated caller acting on their own account. All non-public routes resolve through one permission policy; a non-member of a non-public book gets 404, never 403, so a book's existence cannot be probed.
- Idempotent. Every non-GET requires the
Idempotency-Keyheader (missing or empty: 428idempotency_key_required). Yes means a replay with the same key within 24 hours returns the original result without repeating the side effect. No means each call has a fresh effect (a new grant, a new code). Read means a GET: no header, nothing to replay. - Error envelope. All 4xx and 5xx use
{code, message, details, request_id}. Branch oncode, notmessage. See the integration guide. One exception: a body that fails schema validation returns a Django Ninja 422 with a{"detail": [...]}body, not the envelope. - Feature gate. Only
POST /books/{slug}/submissions/requires the book's plan to includeapi_access; plans without it return 403feature_gatedthere. Every other route works on every plan. - Cursor pagination. All list endpoints return
{items, next_cursor, has_more}. Passnext_cursorback as?cursor=. - Delta sync. Most list endpoints accept
?updated_since=<iso8601>. Pin it to the response'sLast-Modifiedheader, not your client clock. - Optimistic locking. Detail GETs carry an
etagfield. Send it back asIf-Matchon the next PATCH for RFC 7232 lost-update protection. - Deprecation headers. Endpoints scheduled for removal carry
DeprecationandSunsetresponse headers per RFC 8594, with a minimum 90-day notice. See the changelog.
Summary
Routes are grouped by what they act on. A linked endpoint has a note below with the details a one-line summary cannot carry; unlinked routes are fully described by their row plus the conventions above.
Authentication & session
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
POST /auth/ | Mint a bearer token from a logged-in session. | Browser session only | Yes |
DELETE /auth/ | Revoke the calling token. | Token only (no session) | Yes |
GET /auth/ | Current identity and auth method. | Any signed-in user | Read |
POST /me/ | Revoke every token for the calling user. | Any signed-in user | Yes |
Identity & authorization
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
GET /me/ | Current user profile. | Any signed-in user | Read |
PATCH /me/ | Update your name. | Any signed-in user | Yes |
GET /me/ | Per-plan slot snapshot and storage usage. | Any signed-in user | Read |
GET /books/ | Your role, feature flags and permitted actions on a book. | Any signed-in user | Read |
Books
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
GET /books/ | List books where you are owner or admin. | Owner or admin (per row) | Read |
POST /books/ | Create a book from a held quota slot (no card payment). | Any signed-in user; quota gates | Yes |
GET /books/ | Read a book. | Owner or admin | Read |
PATCH /books/ | Update book fields (subtitle, theme, settings). | Owner or admin | Yes |
POST /books/ | Archive a book (hidden from listings, data kept). | Owner only | Yes |
POST /books/ | Restore an archived book. | Owner only | Yes |
DELETE /books/ | Permanently delete a book and everything in it. | Owner only | Yes |
Submissions
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
POST /books/ | Submit a memory (text, media, or both). | Owner or admin | Yes |
GET /books/ | List submissions (cursor and delta sync). | Owner or admin | Read |
GET /books/ | Read a submission. Carries etag. | Owner or admin | Read |
PATCH /books/ | Edit a submission (optional If-Match). | Owner, admin, or the author | Yes |
DELETE /books/ | Permanently delete a submission. | Owner or admin | Yes |
POST /books/ | Approve a pending submission. | Owner or admin | Yes |
POST /books/ | Reject a pending submission. | Owner or admin | Yes |
POST /books/ | Pin an approved submission. | Owner or admin | Yes |
POST /books/ | Unpin a pinned submission. | Owner or admin | Yes |
Media
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
POST /books/ | Upload a file (multipart; per-kind cap, 90 MB ceiling). | Owner, admin, or the author | Yes |
GET /books/ | Read media metadata and a signed download_url. | Owner or admin | Read |
DELETE /books/ | Detach a file and queue cleanup (resets moderation). | Owner or admin | Yes |
Exports
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
POST /books/ | Request an export (returns 202). | Owner or admin | Yes |
GET /books/ | Poll an export job. | Owner or admin | Read |
GET /books/ | Get a 15-minute signed download URL. | Owner or admin | Read |
Collaboration
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
GET /books/ | List members (cursor and delta sync). | Owner or admin | Read |
POST /books/ | Add a member by email or user_id. | Owner or admin | Yes |
DELETE /books/ | Remove a member. | Owner only | Yes |
GET /books/ | List invitations (cursor and status filter). | Owner or admin | Read |
POST /books/ | Invite by email, phone or Telegram. | Owner or admin | Yes |
DELETE /books/ | Revoke a pending invitation. | Owner or admin | Yes |
Messaging & notifications
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
GET /books/ | Per-channel status snapshot (no secrets). | Owner or admin | Read |
GET /books/ | List subscriptions (active and inactive). | Owner or admin | Read |
DELETE /books/ | Unsubscribe a subscription. | Owner, admin, or the subscriber | Yes |
Billing & plan
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
GET /plans/ | Public plan catalog. | Anyone | Read |
GET /books/ | Latest billing record, plan and quota snapshot. | Owner only | Read |
Account & GDPR
| Endpoint | What it does | Who can call it | Idempotent |
|---|---|---|---|
POST /me/ | Queue a GDPR data export. | Any signed-in user | Yes |
GET /me/ | Poll the export; a fresh signed URL on each poll. | The requesting user | Read |
POST /me/ | Delete your account (step-up reauthentication required). | Any signed-in user | Yes |
POST /me/ | Mint a single-use, 5-minute delete grant (password or code). | Any signed-in user | No |
POST /me/ | Send a delete-purpose code to your own email or phone. | Any signed-in user | No |
POST /me/ | Unsubscribe from an email stream. | Any signed-in user | Yes |
POST /me/ | RFC 8058 one-click unsubscribe (onboarding only). | Email token | Yes (token) |
The integrator routes, in 10 groups. All share the /api/ prefix, the error envelope and the Idempotency-Key contract on non-GETs; only POST /books/{slug}/submissions/ carries the api_access plan-feature gate.
Endpoint notes
The routes below need more than a one-liner. Everything else is documented by its summary row plus the conventions.
POST /auth/token/
Mints a new API token for the calling user. The first token must be minted from a logged-in browser session: the API cannot issue its own first token. The response carries the plaintext token once, in plaintext; it is never shown again. An idempotent replay returns plaintext: null and replayed: true, so a retry after a network blip does not fail. name must be unique per user; a collision returns 409 name_taken.
DELETE /auth/token/
Revokes the token that authenticated the call. Session-authenticated callers get 401: a browser session has no current token to revoke. Idempotent: a replay returns 200.
POST /me/sessions/revoke/
The stolen-device switch. Revokes every active API token for the calling user; web sessions are unaffected. The response includes revoked_count; a replay returns the same count without revoking anything further.
PATCH /me/
Updates first_name, last_name and books_sort (the shelf order: activity, title, created or role) only. Email, phone_number and the channel identifiers (telegram_chat_id, telegram_username, imessage_identifier) each have their own verification flow, so sending any of them returns 400 validation_failed. Set your phone through the verified phone-link flow on the web.
GET /books/{slug}/permissions/
Returns {book_slug, role, feature_*, actions} for the calling user on one book: role is owner, admin, contributor or null, the feature_* booleans mirror the book's plan, and actions lists the action names the caller may perform. Use it to drive client affordances instead of probing every endpoint for 403. The AI enhancer flag is spelled feature_ai_enhancer. Non-members of a non-public book get 404, not 403.
GET /books/
Cursor-paginated: {items, next_cursor, has_more}. There is no count. Pass next_cursor back as ?cursor=; has_more: false marks the end. ?updated_since= gives delta sync; pin it to the response's Last-Modified header, not your clock.
POST /books/
Creates a book from a quota slot. If you hold a slot at the requested plan tier, the book is created immediately. If no slot is available and the plan is not the default plan, the response is 402 payment_required with a hint URL to the web checkout. The API never starts a card payment: acquire slots on the web first.
PATCH /books/{slug}/
Partial update. Send If-Match with the etag from your last read for lost-update protection; a mismatch returns 412 precondition_failed (refetch and retry). Set the book's purpose with use_case_slug. While the book's subscription is in its grace period every write returns 403 book_grace_period.
POST /books/{slug}/archive/
Owner only. Hides the book from listings and keeps every story, photo and recording. Archiving an already-archived book is a no-op 200. Undo with POST /books/{slug}/restore/.
DELETE /books/{slug}/
Permanent, owner only. This erases every story, photo, recording and export that contributors added to the book, and there is no undo. If the book might be wanted again, archive it instead.
When the deletion has to be queued (the off-site backup erasure step is temporarily unreachable) the response is 202 deletion_queued: retry the same Idempotency-Key after a backoff, or poll GET /books/{slug}/ until it returns 404. The cascade runs after the response, never inside it.
POST /books/{slug}/submissions/
Creates a memory. Fields: text_content (required), contributor_name, is_private (default false); unknown fields are refused with a 422. The contributor email is always the API key owner's. Replaying the same Idempotency-Key returns the original result, success or failure, without creating a second memory. Attach files afterwards with POST /books/{slug}/submissions/{id}/media/.
GET /books/{slug}/submissions/
Cursor-paginated. ?updated_since=<iso8601> gives delta sync and composes with ?cursor=. Each item carries an etag to send back as If-Match on its next PATCH. The list contains only rows the caller may read; there is nothing to filter client-side.
PATCH /books/{slug}/submissions/{id}/
The one route in this group a contributor can call: a contributor-role member may edit a submission they authored. Moderation actions (approve, reject, pin, unpin, delete) stay owner or admin only. If-Match is optional: when present, a mismatch returns 412; when absent, the last write wins. Adding or removing media through this route resets moderation to pending and unpins the submission before the change lands.
POST /books/{slug}/submissions/{id}/approve/
Approves a pending submission. 423 while the book is in its billing grace period, 400 on a validation error, 402 when a plan limit is exceeded. Approving an already-approved submission returns 200 with the cached result.
POST /books/{slug}/submissions/{id}/reject/
Body: {"reason": "<text>"}. An empty body is accepted. reason is shown to the contributor in their rejection notification.
POST /books/{slug}/submissions/{id}/media/
Multipart upload of one file. Accepts image/*, audio/*, video/*. Photos up to 20 MB, audio up to 50 MB, video up to your plan's limit; the request ceiling is 90 MB. An oversize file returns 413 before any bytes are stored. The file type is detected from its bytes; the declared Content-Type is advisory only.
Callable by the owner, an admin, or the submission's author. The upload resets moderation to pending, clears the share card and unpins the submission before the new file becomes visible. Plan gates: audio needs feature_audio; video needs feature_video or feature_live_photo (a feature_live_photo-only plan accepts QuickTime motion of six seconds or less, not arbitrary video). Without the feature: 400 validation_failed.
GET /books/{slug}/submissions/{id}/media/{media_id}/
Response fields: download_url, download_status, processing_status, mime_type, scan_result; video rows also carry thumbnail_url.
download_urlis a 15-minute presigned URL (it carriesX-Amz-Expires=900). Do not cache it; refetch from this endpoint when it expires.- Wait for
processing_status == "complete"before surfacing the URL. download_url: nullwithdownload_status: "unavailable"means signing failed closed; poll until it becomes available.- Audio and video carry
scan_result: "skipped_by_policy", not"clean", and are served regardless. Never gate playback onscan_result == "clean"alone. thumbnail_urlis a signed poster-frame URL with the same 15-minute TTL and the same fail-closednull; it isnullfor non-video media and until processing completes. Present on both the media read and theMediaSummaryembedded in submission reads.
DELETE /books/{slug}/submissions/{id}/media/{media_id}/
Owner or admin. Resets moderation to pending, detaches the file, and queues cleanup of every derived asset and the storage counters. A replay returns the cached 204.
POST /books/{slug}/exports/
Returns 202 and the export job; rendering happens in the background. Fields:
theme: required. One ofelegant_classic,modern_clean,storybook,memorial_service,wedding_album,baby_first_year,daily_journal,retirement_tribute,botanical_luxe,heirloom_gallery,editorial_noir. An unknown theme returns 400 with the valid list indetails.offered.intent:screen(default; no bleed, no gutter),home(no bleed, 10 mm gutter),shop(3 mm bleed, 15 mm gutter).export_format:pdf(default),json,zip. Each format requires a matching plan feature.
GET /books/{slug}/exports/{job_id}/artifact/
Returns a 15-minute signed download URL. Status codes:
200:{download_url, expires_at}.404: no such job (a job id from another book also surfaces as 404).409 export_not_ready: status is stillpendingorprocessing; poll the status endpoint.410 export_failed: the job failed or the output file is no longer available; request a new export.502 storage_unavailable: the file exists but the signed URL could not be minted right now (storage outage, credential rotation). Retry with exponential backoff (1 s, 2 s, 4 s, 8 s). Distinct from410, where the file itself is gone.
GET /me/export/{job_id}/ returns the same 502 storage_unavailable envelope for a completed GDPR export whose URL cannot be minted; same retry contract.
POST /books/{slug}/members/
Accepts email or user_id (exactly one). role defaults to admin and must be admin or contributor; ownership cannot be transferred here. Error codes: 404 user_not_found, 409 already_a_member, 402 member_limit_exceeded when the plan's member cap is reached.
DELETE /books/{slug}/members/{user_id}/
Owner only. Returns 204 on success and on an already-removed member. Removing the owner returns 400 cannot_remove_owner.
GET /books/{slug}/invitations/
Optional ?status= narrows to pending, accepted, expired or revoked; any other value returns 400 query_param_invalid. Supports ?updated_since= for delta sync.
POST /books/{slug}/invitations/
Fields: identifier_type (email, phone or telegram), identifier, role (default contributor). Identifiers are normalised: emails lower-cased, phones converted to E.164, Telegram handles stripped of a leading @ and lower-cased.
If the identifier already belongs to a user, the membership is created immediately and the returned row has status="accepted"; that path returns 402 member_limit_exceeded when the plan's member cap is reached. Phone and Telegram invitations are accepted when the person messages the book; there is no separate "send" call, this endpoint creates and dispatches the invitation.
DELETE /books/{slug}/invitations/{id}/
Sets status to "revoked" rather than deleting the row, so the audit trail survives. Revoking again returns 200 with already_revoked: true. A revoked invitation does not block re-inviting the same identifier later.
GET /books/{slug}/channels/
One row per channel (web, email, sms, rcs, imessage, whatsapp, telegram) with its contributor-facing details: public phone numbers, mailto addresses, deep links. Provider credentials and webhook secrets are never included.
DELETE /books/{slug}/notifications/subscriptions/{id}/
Deactivates the subscription rather than deleting it. Deleting again returns 200 with already_inactive: true. The subscriber can always cancel their own subscription, whether or not they are a member of the book.
POST /me/export/
Queues a GDPR data export. Returns 202 and a pending job; poll GET /me/export/{job_id}/ until status="complete", then read download_url (a 60-minute signed URL, re-issued on every poll). Limited to one export per day per user: a second request within 24 hours returns 429 rate_limited even with a different Idempotency-Key. Exports are kept for 30 days.
POST /me/delete/
Two phases: your account's personal data is scrubbed inside the request; the books you own, history and the off-site backup erasure follow in the background.
Requires step-up reauthentication: a bearer token alone is rejected with 403 reauth_required. Either send reauth_token (a single-use, 5-minute grant from POST /me/delete/reauth/, minted against your password or a delete-purpose code requested via POST /me/delete/reauth/otp/), or, on a session-authenticated call only, send password. The idempotency replay check runs before the step-up gate, so a retry after a connection blip does not spend the grant twice. If the off-site erasure step is unreachable, the response is 202 deletion_queued; retry the same Idempotency-Key after a backoff (the grant is restored for the retry).
POST /me/unsubscribe/{stream}/
Authenticated (session or bearer token, like every other /me/ route). Streams:
onboarding: the post-signup reminder emails.digests: per-book digests on books you own.activity_reminders: inactivity nudges on books you own.
An unknown stream returns 400 unknown_stream with the supported list in details.offered. A second call returns 200 with already_unsubscribed: true. The Idempotency-Key header is required.
POST /me/unsubscribe/{stream}/token/
RFC 8058 one-click path, unauthenticated. The unsubscribe links in onboarding emails resolve here with ?token=<uuid>. Only the onboarding stream issues these tokens; digests and activity_reminders return 400 stream_not_token_supported on this path, so use the authenticated route for those.
A missing or invalid token returns 401 unauthorized. The token itself is the replay identity, so no Idempotency-Key header is needed.
Next
API getting started walks through your first call end to end. The API changelog lists every change with the date it shipped.
Ready to try it? Create your free memory book