Skip to content

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:

Everything here is documented from real code paths. Endpoints unique to this fork are marked (Standard Red Notes).

Contents

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:

  1. Key params (PKCE). The client generates a random code_verifier, derives code_challenge = base64url(sha256(code_verifier)), and calls POST /v2/login-params with the email and the code_challenge. The server returns the account’s key params (KDF algorithm, salt/nonce, iterations).
  2. 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).
  3. Sign in (PKCE). The client calls POST /v2/login with the email, the password (the derived server password, not the user’s password), and the code_verifier. On success the server returns a session containing an access_token and a refresh_token, plus the account’s key params and user object.
  4. Authenticated requests. Bearer the access token: Authorization: Bearer <access_token>. The FetchRequestHandler sets 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).
  5. Refresh. When the access token expires, call POST /v1/sessions/refresh with { access_token, refresh_token } to receive a new pair. The session body shape is in SessionRefreshResponseBody.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_at timestamps, 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/items is resolved to items/sync (see ItemsController); 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.

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.

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.