HTTP API reference
This document describes the HTTP API exposed by a self-hosted Standard Red Notes server. It is generated from the source of truth in this repository:
- The API gateway route map —
server/packages/api-gateway/src/Service/Resolver/EndpointResolver.tsand the gateway controllers underserver/packages/api-gateway/src/Controller. - The client path map the official clients actually call —
app/packages/snjs/lib/Services/Api/Paths.tsand the published@standardnotes/apiServer/*/Paths.tsmodules. - The auth and syncing-server controllers under
server/packages/auth/src/Controllerandserver/packages/syncing-server.
Everything here is documented from real code paths. Endpoints unique to this fork are marked (Standard Red Notes).
Contents
- Base URL and versioning
- Authentication model
- How to call the API
- End-to-end encryption implications
- Endpoints
- Authentication and sessions
- Account recovery
- Users, settings and features
- Sync and items
- Revisions
- Files
- WebSocket realtime
- Subscriptions and offline tokens
- Two-factor: authenticators and magic link
- Collaboration: shared vaults, invites, messages
- App passwords (Standard Red Notes)
- MCP tokens (Standard Red Notes)
- Trusted devices and push MFA (Standard Red Notes)
- Public share links (Standard Red Notes)
- Dead man’s switches (Standard Red Notes)
- Email reminders (Standard Red Notes)
- AI assistant proxy (Standard Red Notes)
- Integrations (Standard Red Notes)
- Admin (Standard Red Notes)
- Server metadata
Base URL and versioning
All requests go to the API gateway. In the bundled Docker stack everything
enters through the single app front door on http://localhost:3001: its nginx
serves the web client and proxies /v1, /v2, /auth, /subscription, and
/healthcheck to the gateway, /files/ to the files service (prefix-stripped),
and /sockets to the realtime websocket - the gateway and files service publish
no host ports of their own. In a production deployment you put a reverse proxy
in front of that one port and use your own domain. The examples below use
$SERVER for the gateway origin:
export SERVER="http://localhost:3001"
Paths are versioned by a leading /v1 or /v2 segment. The clients send a
payload-level API version field too (api_version), defined in
app/packages/api/src/Domain/Api/ApiVersion.ts:
v0 = 20200115, v1 = 20240226. The legacy snjs client sends 20240226.
The current sign-in flow is /v2 (PKCE). The older /v1/login path still
exists and resolves to the same PKCE handler on the gateway.
Authentication model
Standard Red Notes uses the Standard Notes authentication protocol. The account password is never sent to the server as-is and is never used to decrypt on the server. The high-level flow:
- Key params (PKCE). The client generates a random
code_verifier, derivescode_challenge = base64url(sha256(code_verifier)), and callsPOST /v2/login-paramswith the email and thecode_challenge. The server returns the account’s key params (KDF algorithm, salt/nonce, iterations). - Local key derivation. The client derives the root key from the user’s password + key params on device (argon2). It splits that into a “server password” (used only to prove knowledge to the server) and a master key (never leaves the device, used to encrypt/decrypt items).
- Sign in (PKCE). The client calls
POST /v2/loginwith the email, thepassword(the derived server password, not the user’s password), and thecode_verifier. On success the server returns a session containing anaccess_tokenand arefresh_token, plus the account’s key params and user object. - Authenticated requests. Bearer the access token:
Authorization: Bearer <access_token>. TheFetchRequestHandlersets this header from the session access token. Browser clients additionally rely on session cookies set by the server (see the self-hosting guide on cookie configuration). - Refresh. When the access token expires, call
POST /v1/sessions/refreshwith{ access_token, refresh_token }to receive a new pair. The session body shape is inSessionRefreshResponseBody.ts:{ session: { access_token, refresh_token, access_expiration, refresh_expiration, readonly_access } }.
Obtaining access (credentials and tokens)
- Account credentials — email + password (registered via
POST /v1/users) are the primary way to obtain a session. Two-factor (TOTP authenticator or email magic link) may be required as a second factor at sign-in. - App passwords (Standard Red Notes) — a per-account secret that satisfies the interactive 2FA challenge for a single sign-in, so headless clients do not need a live TOTP code. The account password is still required. See App passwords.
- MCP tokens (Standard Red Notes) — the API issues an authentication
credential (
<uuid>.<auth-secret>); the client adds a separate wrap secret to form the full bridge token. The full token authenticates without the account email/password and unwraps client-wrapped items keys. See MCP tokens. - Trusted devices (Standard Red Notes) — a per-device token that bypasses only the 2FA gate (never the account password) on future sign-ins. See Trusted devices.
How to call the API (curl)
The full sign-in flow requires deriving the server password locally (argon2),
so the simplest faithful client is the bundled srn-client CLI (see
cli/srn-client), which runs the real protocol
via an embedded snjs client. The raw HTTP sketch below shows the request/response
shapes; replace the derivation step with the snjs/srn-client logic for a real
call.
SERVER="http://localhost:3001"
EMAIL="me@example.com"
# 1) Get key params (PKCE). Generate a code_verifier and its SHA-256 challenge.
# (Pseudocode: code_challenge = base64url(sha256(code_verifier)))
curl -s -X POST "$SERVER/v2/login-params" \
-H 'Content-Type: application/json' \
-d "{\"api_version\":\"20240226\",\"email\":\"$EMAIL\",\"code_challenge\":\"<challenge>\"}"
# -> { "identifier": ..., "pw_nonce": ..., "version": ..., ... key params }
# 2) Derive the root key locally from your password + key params (argon2),
# split into the server password. Then sign in:
curl -s -X POST "$SERVER/v2/login" \
-H 'Content-Type: application/json' \
-d "{\"api_version\":\"20240226\",\"email\":\"$EMAIL\",\"password\":\"<server_password>\",\"code_verifier\":\"<code_verifier>\",\"ephemeral\":false}"
# -> { "session": { "access_token": "...", "refresh_token": "...", ... },
# "key_params": { ... }, "user": { ... } }
# 3) Make an authenticated request with the access token:
ACCESS_TOKEN="<from step 2>"
curl -s -X GET "$SERVER/v1/sessions" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# -> [ { "uuid": ..., "api_version": ..., "user_agent": ..., ... }, ... ]
End-to-end encryption implications
Item payloads are ciphertext. When you push items to POST /v1/items the
content and enc_item_key fields are already encrypted on the client with
keys derived from the account password; the server stores and relays them
without being able to read them. Likewise file contents, share payloads, and
MCP key material are stored as ciphertext or as opaque client-side wrappings.
Practical consequences when talking to the API directly:
- You cannot construct a valid item payload server-side — you must encrypt with
the user’s keys (use snjs /
srn-client). - The server-visible metadata is limited: item
uuid,content_type,created_at/updated_attimestamps, item existence and size, the account email, and session/device info. It cannot see note titles or contents. - Public share links store only ciphertext keyed by a
shareId; the decryption key lives in the link fragment and never reaches the server.
Endpoints
Notation: each entry shows the client-facing method + path (what you call on
the gateway). The “Resolver id” is the internal identifier the gateway maps the
route to in
EndpointResolver.ts;
it is included for traceability. Unless noted, request/response bodies are JSON.
Authenticated endpoints require Authorization: Bearer <access_token> (and, for
browser sessions, the session cookies).
Authentication and sessions
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v2/login |
auth.pkceSignIn |
PKCE sign-in. Body: email, password (derived server password), code_verifier, ephemeral, optional hvm_token, workspace_identifier. Returns { session, key_params, user }. |
| POST | /v2/login-params |
auth.pkceParams |
PKCE key params. Body: api_version, email, code_challenge, optional mfa_code, app_password, trusted_device_token, workspace_identifier. Returns the account key params. |
| POST | /v1/login |
auth.pkceSignIn |
Legacy alias for PKCE sign-in (same handler as /v2/login). |
| GET | /v1/login-params |
auth.pkceParams |
Legacy alias for key params. Optional cross-service token. |
| POST | /v1/logout |
auth.signOut |
Sign out the current session. |
| GET | /v1/sessions |
auth.sessions.list |
List the user’s active sessions. Authenticated. |
| DELETE | /v1/sessions/:uuid |
auth.sessions.delete |
Revoke a specific session. Authenticated. |
| DELETE | /v1/sessions |
auth.sessions.deleteAll |
Revoke all other sessions. Authenticated. |
| POST | /v1/sessions/refresh |
auth.sessions.refresh |
Body: access_token, refresh_token. Returns a refreshed session pair. |
| POST | sessions/validate |
auth.sessions.validate |
Internal: session validation used by gateway middleware. |
Account recovery
Two different mechanisms use similar names:
- MFA recovery codes satisfy or restore a server-side second-factor challenge. They do not contain account encryption keys and cannot replace a forgotten account password.
- Optional account recovery (v2 escrow) is a Standard Red Notes client flow. A signed-in client encrypts root-key material with a separately generated high-entropy code. A signed-out client uses the code’s UUID locator to fetch the bounded ciphertext, decrypts it locally, obtains a normal session with the recovered root key, and rotates credentials through the authenticated credentials endpoint.
The server never receives the v2 recovery secret or wrapping key. The only public lookup takes a UUID locator and returns a generic unavailable response for absent, malformed, or legacy escrow. The UI under Preferences -> Security -> Account recovery is the supported way to enroll, rotate, disable, and exercise this contract.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/account-recovery/lookup |
auth.accountRecovery.lookup |
Public, rate-limited v2 escrow lookup. Body: user_uuid from the complete recovery code. Returns only { escrow, identifier, workspace_identifier } for a valid bounded record; the code secret is never sent. |
| POST | /v1/recovery/codes |
auth.generateRecoveryCodes |
Legacy/MFA recovery-code generation. Requires cross-service token + x-server-password; this is not v2 password recovery. |
| POST | /v1/recovery/login |
auth.signInWithRecoveryCodes |
Legacy/MFA recovery sign-in. Body: api_version, username, password, code_verifier, recovery_codes, optional hvm_token. It still requires password-derived material. |
| POST | /v1/recovery/login-params |
auth.recoveryKeyParams |
Legacy/MFA key-params request. Body: api_version, username, code_challenge, recovery_codes. |
Users, settings and features
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/users (POST /auth) |
auth.users.register |
Register. Body: key params content + email, password (server password), api_version, ephemeral, optional hvm_token. Returns a session. |
| PUT | /v1/users/:userUuid/attributes/credentials |
auth.users.updateCredentials |
Change email/password (re-wraps keys). Authenticated. |
| DELETE | /v1/users/:userUuid |
auth.users.delete |
Delete the account. |
| GET | /v1/users/:userUuid/settings |
auth.users.getSettings |
List the user’s settings. |
| PUT | /v1/users/:userUuid/settings |
auth.users.updateSetting |
Upsert a setting (name, value). |
| GET | /v1/users/:userUuid/settings/:settingName |
auth.users.getSetting |
Read one setting. |
| DELETE | /v1/users/:userUuid/settings/:settingName |
auth.users.deleteSetting |
Delete one setting. |
| PUT | /v1/users/:userUuid/subscription-settings |
auth.users.updateSubscriptionSetting |
Update a subscription setting. |
| GET | /v1/users/:userUuid/subscription-settings/:subscriptionSettingName |
auth.users.getSubscriptionSetting |
Read one subscription setting. |
| GET | /v1/users/:userUuid/features |
auth.users.getFeatures |
List the account’s feature entitlements (all included in this fork). |
| GET | /v1/users/:userUuid/subscription |
auth.users.getSubscription |
Read the account subscription (synthetic full-access in this fork). |
| GET | /v1/users/:userUuid/mfa-secret |
auth.users.getMfaSecret |
Read the MFA secret. Authenticated. |
| POST | /v1/users/:userUuid/requests |
auth.users.createRequest |
Create a user request (e.g. account-deletion / data export request). |
Credential changes atomically invalidate the current account-recovery escrow. After a successful password change or recovery, the client must explicitly re-enroll and show a new one-time code. There is no administrator-only endpoint that installs a new password or returns decrypted recovery material.
Sync and items
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/items |
sync.items.sync |
The core sync endpoint. Body: items (encrypted payloads), sync_token, cursor_token, limit, optional shared_vault_uuids, api_version. Returns retrieved/saved/conflicted items and new sync tokens. Item payloads are ciphertext. |
| POST | /v1/items/check-integrity |
sync.items.check_integrity |
Compare client/server item hashes to detect drift. |
| GET | /v1/items/:uuid |
sync.items.get_item |
Fetch a single item by uuid (ciphertext). |
The gateway route
POST /v1/itemsis resolved toitems/sync(seeItemsController); the snjs client path constant is/v1/items.
Revisions
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v2/items/:itemUuid/revisions |
revisions.revisions.getRevisions |
List stored revisions for an item. |
| GET | /v2/items/:itemUuid/revisions/:id |
revisions.revisions.getRevision |
Fetch one revision (ciphertext). |
| DELETE | /v2/items/:itemUuid/revisions/:id |
revisions.revisions.deleteRevision |
Delete one revision. |
Files
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/files/valet-tokens |
auth.valet-tokens.create |
Mint a valet token authorizing an upload/download/delete against the files service. The actual chunked transfer happens against the files host the gateway advertises in meta.server.filesServerUrl (PUBLIC_FILES_SERVER_URL, default http://localhost:3001/files — the app front door’s prefix-strip proxy). |
File chunk operations (handled by the files service, authorized with the valet
token from above; client paths in snjs/.../Paths.ts, joined onto the files
host above, e.g. http://localhost:3001/files/v1/files/upload/chunk):
POST /v1/files/upload/create-session, POST /v1/files/upload/chunk,
POST /v1/files/upload/close-session, GET/DELETE /v1/files, plus the
shared-vault variants under /v1/shared-vault/files/*.
WebSocket realtime
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/sockets/tokens |
sockets/tokens |
Create a WebSocket connection token. Requires cross-service token. Used to authenticate the realtime gateway connection. |
| POST | /v1/sockets/connections |
sockets/connections/:connectionId |
Register a connection (requires connectionid header). |
| DELETE | /v1/sockets/connections |
sockets/connections/:connectionId |
Deregister a connection. |
| GET | /v1/sockets/sync/capabilities |
local controller | Advertise protocol-v1 worker sync only when exact origin, durable backend, and shared Redis state are ready; otherwise returns an empty capability list. |
| POST | /v1/sockets/sync/ticket |
local controller | Mint a short-lived, one-use sync ticket for the authenticated session and validated deviceId; returns 503 SYNC_DISABLED when unavailable. |
The worker upgrades the exact /sockets/sync path with an allowed Origin and
no query string. It sends the one-use ticket in the first protocol AUTH frame;
credentials and tickets are never put in this URL. HTTP item sync is the
fallback when this capability is absent or the socket disconnects.
Subscriptions and offline tokens
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/subscription-tokens |
auth.subscription-tokens.create |
Mint a short-lived subscription token (used by extensions/services). |
| POST | /v1/subscription-invites |
auth.subscriptionInvites.create |
Create a subscription-sharing invite. |
| GET | /v1/subscription-invites |
auth.subscriptionInvites.list |
List invites. |
| DELETE | /v1/subscription-invites/:inviteUuid |
auth.subscriptionInvites.delete |
Cancel an invite. |
| POST | /v1/subscription-invites/:inviteUuid/accept |
auth.subscriptionInvites.accept |
Accept an invite. |
| GET | /v1/offline/features |
auth.offline.features |
Offline feature list for an offline subscription token. |
| POST | /v1/offline/subscription-tokens |
auth.offline.subscriptionTokens.create |
Mint an offline subscription token. |
| GET | /v1/offline/users/subscription |
auth.users.getOfflineSubscriptionByToken |
Look up an offline subscription by token. |
In this fork every account has full access regardless of subscription (features mode
included), so these endpoints exist for protocol compatibility but full access does not depend on them.
Two-factor: authenticators and magic link
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v1/authenticators/ |
auth.authenticators.list |
List registered WebAuthn authenticators. |
| GET | /v1/authenticators/generate-registration-options |
auth.authenticators.generateRegistrationOptions |
Begin WebAuthn registration. |
| POST | /v1/authenticators/verify-registration |
auth.authenticators.verifyRegistrationResponse |
Complete WebAuthn registration. |
| POST | /v1/authenticators/generate-authentication-options |
auth.authenticators.generateAuthenticationOptions |
Begin WebAuthn authentication. |
| DELETE | /v1/authenticators/:authenticatorId |
auth.authenticators.delete |
Remove an authenticator. |
| POST | /v1/mfa/magic-link/request |
auth.magicLink.request |
Request delivery of an email magic-link code. Fails when email delivery is unavailable; the code is never returned in the response. |
| POST | /v1/mfa/magic-link/status |
auth.magicLink.setStatus |
Enable/disable magic-link 2FA. Enabling requires configured email delivery. |
| GET | /v1/mfa/magic-link/status |
auth.magicLink.getStatus |
Read magic-link 2FA status. |
Collaboration: shared vaults, invites, messages
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v1/shared-vaults/ |
sync.shared-vaults.get-vaults |
List shared vaults. |
| POST | /v1/shared-vaults/ |
sync.shared-vaults.create-vault |
Create a shared vault. |
| DELETE | /v1/shared-vaults/:sharedVaultUuid |
sync.shared-vaults.delete-vault |
Delete a shared vault. |
| POST | /v1/shared-vaults/:sharedVaultUuid/valet-tokens |
sync.shared-vaults.create-file-valet-token |
Mint a valet token for a vault file. |
| POST | /v1/shared-vaults/:sharedVaultUuid/invites |
sync.shared-vault-invites.create |
Invite a contact to a vault. |
| PATCH | /v1/shared-vaults/:sharedVaultUuid/invites/:inviteUuid |
sync.shared-vault-invites.update |
Update an invite. |
| POST | /v1/shared-vaults/:sharedVaultUuid/invites/:inviteUuid/accept |
sync.shared-vault-invites.accept |
Accept an invite. |
| POST | /v1/shared-vaults/:sharedVaultUuid/invites/:inviteUuid/decline |
sync.shared-vault-invites.decline |
Decline an invite. |
| GET | /v1/shared-vaults/invites |
sync.shared-vault-invites.get-user-invites |
List the user’s invites. |
| GET | /v1/shared-vaults/invites/outbound |
sync.shared-vault-invites.get-outbound |
List sent invites. |
| GET | /v1/shared-vaults/:sharedVaultUuid/invites |
sync.shared-vault-invites.get-vault-invites |
List a vault’s invites. |
| DELETE | /v1/shared-vaults/:sharedVaultUuid/invites/:inviteUuid |
sync.shared-vault-invites.delete-invite |
Delete one invite. |
| DELETE | /v1/shared-vaults/:sharedVaultUuid/invites |
sync.shared-vault-invites.delete-all |
Delete a vault’s invites. |
| DELETE | /v1/shared-vaults/invites/inbound |
sync.shared-vault-invites.delete-inbound |
Delete inbound invites. |
| DELETE | /v1/shared-vaults/invites/outbound |
sync.shared-vault-invites.delete-outbound |
Delete outbound invites. |
| GET | /v1/shared-vaults/:sharedVaultUuid/users |
sync.shared-vault-users.get-users |
List vault members. |
| DELETE | /v1/shared-vaults/:sharedVaultUuid/users/:userUuid |
sync.shared-vault-users.remove-user |
Remove a member. |
| POST | /v1/shared-vaults/:sharedVaultUuid/users/:userUuid/designate-survivor |
sync.shared-vault-users.designate-survivor |
Designate a survivor for the vault. |
| GET | /v1/messages/ |
sync.messages.get-received |
Inbound asymmetric (key-exchange) messages. |
| GET | /v1/messages/outbound |
sync.messages.get-sent |
Outbound asymmetric messages. |
| POST | /v1/messages/ |
sync.messages.send |
Send an asymmetric message. |
| DELETE | /v1/messages/inbound |
sync.messages.delete-all |
Delete all inbound messages. |
| DELETE | /v1/messages/:messageUuid |
sync.messages.delete |
Delete one message. |
App passwords (Standard Red Notes)
App-specific passwords let headless clients satisfy the 2FA challenge without a
live TOTP code. The account password is still required. Source:
AppPasswordsController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v1/app-passwords/ |
auth.appPasswords.list |
List app passwords (metadata only; secrets are never returned again). |
| POST | /v1/app-passwords/ |
auth.appPasswords.create |
Body: label. Returns { appPassword: { uuid, label, createdAt }, password } — the plaintext password is shown once. |
| DELETE | /v1/app-passwords/:appPasswordId |
auth.appPasswords.delete |
Revoke an app password. |
To use an app password, present it as app_password in the POST /v2/login-params
body; a valid value marks the interactive 2FA challenge satisfied for that sign-in.
MCP tokens (Standard Red Notes)
The API-facing token (<uuid>.<auth-secret>) authenticates without the account
email/password and returns client-side-wrapped items keys. The web client
appends a client-only wrap secret to create the full three-part bridge token.
Source:
McpTokensController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v1/mcp-tokens/ |
auth.mcpTokens.list |
List MCP tokens (metadata only). |
| POST | /v1/mcp-tokens/ |
auth.mcpTokens.create |
Body: label, scope (read/write), optional scopeTagUuids, plus the client-side wrappedKeys, kdfSalt, kdfParams. Returns the API authentication token once in <uuid>.<auth-secret> form. |
| DELETE | /v1/mcp-tokens/:mcpTokenId |
auth.mcpTokens.delete |
Revoke a token. |
| GET | /v1/mcp-tokens/keys/:mcpTokenId |
auth.mcpTokens.getKeys |
Fetch the wrapped key material + scope for a token. Authenticated. |
| POST | /v1/mcp-tokens/authenticate |
auth.mcpTokens.authenticate |
Unauthenticated (the token is the credential). Body: token, optional apiVersion. Returns { session, key_params, user, mcp_scope, mcp_key_material } — a real session plus wrapped keys in one round trip. scope=read yields a read-only session. |
Trusted devices and push MFA (Standard Red Notes)
A trusted-device token bypasses only the 2FA gate (never the account
password) on future sign-ins. Push-MFA approvals let another signed-in device
approve a pending sign-in. Sources:
TrustedDevicesController.ts,
PendingMfaApprovalsController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/trusted-devices/ |
auth.trustedDevices.create |
Register the current device as trusted; returns a device token. |
| GET | /v1/trusted-devices/ |
auth.trustedDevices.list |
List trusted devices. |
| DELETE | /v1/trusted-devices/:deviceId |
auth.trustedDevices.delete |
Revoke a trusted device. |
| GET | /v1/pending-mfa-approvals/ |
auth.pendingMfaApprovals.list |
List pending sign-in approvals. |
| POST | /v1/pending-mfa-approvals/:challengeId/resolve |
auth.pendingMfaApprovals.resolve |
Approve/deny a pending sign-in. |
| GET | /v1/pending-mfa-approvals/:challengeId/status |
auth.pendingMfaApprovals.status |
Poll a pending sign-in’s status. |
To use a trusted-device token at sign-in, present it as trusted_device_token
in the POST /v2/login-params body. The server fails closed: a wrong/expired
token is ignored and the normal 2FA prompt still appears.
Public share links (Standard Red Notes)
A signed-in user can publish a note as ciphertext keyed by a shareId; the
decryption key lives only in the link fragment and never reaches the server.
Source: SharesController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/shares/ |
auth.shares.create |
Body: type, encryptedPayload, optional nickname, oneTimeView, viewExpiresMinutes. Returns { shareId, share }. Authenticated. |
| GET | /v1/shares/ |
auth.shares.list |
List the user’s shares. Authenticated. |
| DELETE | /v1/shares/:shareId |
auth.shares.revoke |
Revoke a share. Authenticated. |
| GET | /v1/shares/:shareId |
auth.shares.get |
Public, unauthenticated read of the opaque ciphertext. Returns 404 when missing or revoked; never leaks the owner uuid. |
Dead man’s switches (Standard Red Notes)
A survivor switch: the server stores a full share URL (link + key) and emails it
to a recipient if the user stops checking in by the deadline. Source:
DeadManSwitchesController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/dead-man-switches/ |
auth.deadManSwitches.create |
Create a switch (recipient, deadline, share URL). |
| GET | /v1/dead-man-switches/ |
auth.deadManSwitches.list |
List switches. |
| POST | /v1/dead-man-switches/:switchId/check-in |
auth.deadManSwitches.checkIn |
Reset the deadline (“I’m alive”). |
| DELETE | /v1/dead-man-switches/:switchId |
auth.deadManSwitches.delete |
Delete a switch. |
Email reminders (Standard Red Notes)
Reminders the server may email to the account email when due. Unlike in-app
reminders (E2E-encrypted in note appData), the time + message here are stored in
plaintext because the user opted that reminder into email delivery. Source:
EmailRemindersController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| POST | /v1/email-reminders/ |
auth.emailReminders.create |
Create an email reminder (time + message, plaintext). |
| GET | /v1/email-reminders/ |
auth.emailReminders.list |
List email reminders. |
| DELETE | /v1/email-reminders/:reminderId |
auth.emailReminders.delete |
Delete an email reminder. |
Published reminder delivery (Standard Red Notes)
Authenticated users may explicitly publish individual reminders for server-side
delivery. The published message, due time, channel, and destination are plaintext
by design; ordinary note content remains end-to-end encrypted. All management
routes except opt-out require both the server master switch and the user’s synced
opt-in setting. Source:
ReminderDeliveryController.ts.
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /v1/reminder-delivery/config |
Authenticated | Report the server master switch, user opt-in, and effective availability. |
| POST | /v1/reminder-delivery/opt-out |
Authenticated | Ungated authoritative revocation. Cancels durable queued work, removes published plaintext and destination data, and deletes delivery configuration. Returns alreadyDispatched: true if a provider had already accepted an occurrence. |
| GET | /v1/reminder-delivery/delivery-config |
Authenticated | Read the user’s channel and destination configuration. Requires both gates. |
| PUT | /v1/reminder-delivery/delivery-config |
Authenticated | Replace the user’s channel and destination configuration. Refuses unsafe changes while a provider request is in flight or already accepted. |
| GET | /v1/reminder-delivery/ |
Authenticated | List the user’s explicitly published reminders. Requires both gates. |
| POST | /v1/reminder-delivery/ |
Authenticated | Publish or update one reminder. A delivery-affecting change durably cancels the old occurrence before replacement. |
| DELETE | /v1/reminder-delivery/:id |
Authenticated | Cancel and remove one published reminder. Returns a conflict if the occurrence can no longer be recalled safely. |
AI assistant proxy (Standard Red Notes)
A stateless LLM streaming proxy. Notes are E2E-encrypted, so the agent loop and
all tools run in the browser; this controller only forwards one model turn at a
time using a server-held provider key. Source:
AssistantController.ts.
These routes are not part of the home-server EndpointResolver map.
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /v1/assistant/config |
Public | Returns which providers the server has configured (non-sensitive) and the defaults. |
| GET | /v1/assistant/models?provider=... |
Authenticated | Lists models the configured provider offers (queried with the server key). |
| GET | /v1/assistant/usage |
Authenticated | Returns { used, limit, resetsAt } for the per-user daily request budget. |
| POST | /v1/assistant/stream |
Authenticated | Body: system, messages, tools. Streams Server-Sent Events. The server resolves USER > ROLE > default profile and ignores caller provider/model/profile hints. Enforces per-user daily limits; 403 if AI disabled, 429 if over limit. |
Integrations (Standard Red Notes)
Source:
IntegrationsController.ts.
Not part of the EndpointResolver map.
| Method | Path | Auth | Notes |
|---|---|---|---|
| POST | /v1/integrations/github/publish |
Authenticated | Pushes a single note (already converted to Markdown by the client) to a GitHub repo using a user-supplied PAT. Receives decrypted content + the PAT, forwards to GitHub, and persists/logs neither. |
Admin (Standard Red Notes)
In-app admin panel endpoints, gated server-side on the internal-team role.
Source:
AdminController.ts.
| Method | Path | Resolver id | Notes |
|---|---|---|---|
| GET | /v1/admin/lookup-user/:email |
admin.lookupUser |
Look up a user by email. |
| GET | /v1/admin/users/:userUuid/feature-flags |
admin.getUserFeatureFlags |
Read a user’s feature flags. |
| PUT | /v1/admin/users/:userUuid/feature-flags |
admin.setUserFeatureFlag |
Set a feature flag. |
| GET | /v1/admin/users/:email/ban-status |
admin.getUserBanStatus |
Read a user’s ban status. |
| PUT | /v1/admin/users/:userUuid/ban-status |
admin.setUserBanStatus |
Set ban status. |
| GET | /v1/admin/registration |
admin.getRegistrationFlag |
Read whether open registration is enabled. |
| PUT | /v1/admin/registration |
admin.setRegistrationFlag |
Toggle open registration. |
Email delivery administration.
These admin-only endpoints manage the advanced email-delivery subsystem. The
full Redis-backed topology supports prioritized SMTP, SendGrid, Mailgun, and AWS
SES profiles, per-profile rate limits, optional fallback, an encrypted durable
queue, and redacted attempt logs. POST /test is owned by AdminController; the
other routes are owned by the isolated
AdminEmailDeliveryController.ts
boundary.
| Method | Path | Request / query | Notes |
|---|---|---|---|
| GET | /v1/admin/email-delivery/relays |
No query | Returns { relays, fallbackPolicy, configured }; credentials are represented only by credentialsConfigured. |
| PUT | /v1/admin/email-delivery/relays |
{ relays, fallbackPolicy: { mode: "next-enabled" \| "none" } } |
Replaces the ordered profile configuration. Omitted write-only credentials are preserved; explicit null clears them. |
| POST | /v1/admin/email-delivery/test |
{ recipient, relayId? } |
Sends a redacted test through a selected relay or normal priority/fallback order. Returns only accepted, relay identity/kind, and outcome. |
| GET | /v1/admin/email-delivery/queue |
state=ready\|leased\|dead, optional limit and cursor |
Returns queue state, source, attempt counts, times, and safe failure classification; never recipient or message content. |
| GET | /v1/admin/email-delivery/logs |
Optional limit, cursor, relayId, and outcome=sent\|rejected\|transient-failure\|permanent-failure\|rate-limited |
Returns bounded delivery-attempt metadata; never recipient, message content, credentials, or a raw provider response. |
| POST | /v1/admin/email-delivery/queue/:id/retry |
Empty body | Requeues an eligible job and returns its redacted queue record with 202; a leased or otherwise ineligible record returns 409. |
| DELETE | /v1/admin/email-delivery/queue/:id |
Empty body | Discards an eligible job with 204; a currently leased record returns 409. |
The single/home in-memory topology has no advanced relay, queue, or log service:
those routes return 501, while the shared /test route retains its compatible
direct SMTP behavior. 503 means the advanced service exists in the current
topology but is temporarily unavailable. Provider failures are sanitized and
directed to the redacted log; administrative audit events contain operation
metadata only.
Server metadata
| Method | Path | Notes |
|---|---|---|
| GET | /v1/meta |
Public. Returns server metadata such as the CAPTCHA UI URL. |
See also
- Onboarding guide — using the app, accounts, editors, privacy.
- Self-hosting guide — env vars, reverse proxy, cookies/auth, backups.
cli/srn-client— a real, end-to-end-encrypted CLI client that exercises this API via an embedded snjs client.- MCP Bridge — the implemented bridge runtime that uses MCP tokens.
- Security and Account — the user-visible account-recovery lifecycle and trust boundary.