Skip to content
VYLK

API reference

Build a client for your own VYLK instance. Read the client protocol guide for authentication, offline sync, and encryption.

Download OpenAPI specification

Create a session

POST /api/login

Read bootstrap first. In legacy/preparing mode, send the configured server password. In encrypted/cleaning mode, omit password and send a derived proof or recovery_proof. Recovery proof takes precedence if both proofs are supplied. Limit: 16 KiB JSON. Preserve both response cookies in your cookie jar.

Request headers

HeaderTypeRequiredDetails
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: Login

FieldTypeRequiredDetails
passwordstringNoServer password, legacy/preparing only.
proofProofNo32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofNo32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43

Unknown fields are rejected.

{
  "password": "your-server-password"
}

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Set-Cookie200May contain multiple Set-Cookie lines. session is HttpOnly, Path=/, SameSite=Lax, 180-day lifetime; vylk-device groups browser-profile sessions and lasts 365 days. Secure is set for HTTPS. Authenticated activity periodically renews session. Never combine Set-Cookie lines with commas.
Retry-After429Delay in seconds.

Responses

200 Signed in. Sets session and vylk-device cookies.

Content-Type: application/json

Schema: LoginResult

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 invalid_credentials

Content-Type: application/json

Schema: Error

429 login_rate_limited. Wait retry_after seconds; do not hammer credentials.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Sign out this browser profile

POST /api/logout

Revokes all sessions grouped under a valid session's device, not just this tab. Missing, expired, or unrecognized sessions still clear the cookie successfully. Keep encrypted pending edits locally; logout does not upload them.

Request headers

None.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Set-Cookie204May contain multiple Set-Cookie lines. session is HttpOnly, Path=/, SameSite=Lax, 180-day lifetime; vylk-device groups browser-profile sessions and lasts 365 days. Secure is set for HTTPS. Authenticated activity periodically renews session. Never combine Set-Cookie lines with commas.

Responses

204 Signed out; empty body. Clears session cookie.
default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Check session and server identity

GET /api/check

Compare instance_id, vault_mode, and vault_epoch against persisted local state before resuming synchronization. revision is the application asset revision, not a note revision.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Current server identity.

Content-Type: application/json

Schema: CheckResult

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

List signed-in devices

GET /api/devices

Tabs sharing a vylk-device cookie appear as one device. last_seen_at is approximate; activity updates are throttled. Entries are ordered by recent activity.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Active browser-profile devices.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
devicesarray of DeviceYes
401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Revoke a device's sessions

DELETE /api/devices/{id}

Idempotent for an unknown but valid device ID. Revocation cannot erase plaintext or keys already held by an offline device. Disabled by VYLK_DISABLE_VAULT_CHANGES.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

ParameterTypeRequiredDetails
idstringYesDevice ID, not a sync queue device_id. pattern: "^[a-f0-9]{32}$"

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

204 Sessions revoked; empty body.
400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

List note summaries

GET /api/notes

With limit, returns a page; without limit, returns an array and ignores cursor. Newest updated_at first. Plaintext tag filtering is exact; encrypted mode rejects tag with 409 client_search_required. Encrypted responses contain envelopes instead of title/tags/filename. The page response's nextCursor may be absent or empty at the end.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

None.

Query parameters

ParameterTypeRequiredDetails
limitintegerNoEnable pagination. An empty limit is invalid in encrypted mode. minimum: 1 maximum: 100
cursorstringNoOpaque nextCursor from the preceding page; do not parse or invent it.
tagstringNoExact tag; plaintext mode only.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Summaries, never note bodies.

Content-Type: application/json

Schema: array of NoteSummary or NotesPage

Variants:

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 client_search_required: decrypt summaries and search/filter/index tags locally.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Create or replace a complete note

POST /api/notes

Not a patch or text delta. Plaintext limit: 4 MiB JSON; encrypted limit: 8 MiB. Plaintext id may be omitted to generate an ID; supplying an ID requires base_revision (0 for a new note). Encrypted mode requires a client-generated ID, base_revision, epoch, summary, and body. Responses omit content/body. For offline clients prefer ordered sync/push, which supports replay acknowledgements and an instance fence.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: SaveNote or SaveEncryptedNote

Variants:

{
  "id": "my-note",
  "base_revision": 0,
  "title": "First note",
  "tags": "personal,ideas",
  "content": "# First note\n\nHello."
}

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Saved metadata (also for creation; not 201).

Content-Type: application/json

Schema: NoteSummary

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 note_revision_conflict or stale_vault_epoch. Plaintext revision conflicts include note_id and current_revision; encrypted direct conflicts may contain only error/code.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

503 Pending file recovery prevents writes. Retain operations and retry with backoff after recovery; code depends on route (note_file_recovery_blocked, note_recovery_pending, note_recovery_failed).

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Read a complete note

GET /api/notes/{id}

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

ParameterTypeRequiredDetails
idIDYesNote identifier. pattern: "^[A-Za-z0-9_-]{1,64}$"

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Metadata and Markdown content, or summary/body envelopes in encrypted mode.

Content-Type: application/json

Schema: CompleteNote

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

404 note_not_found or staged_note_not_found.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Delete a note at a known revision

DELETE /api/notes/{id}

Revision-checked deletion; not an unconditional idempotent delete. A repeat request may return conflict or not-found. Offline clients should use sync/push.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

ParameterTypeRequiredDetails
idIDYesNote identifier. pattern: "^[A-Za-z0-9_-]{1,64}$"

Query parameters

ParameterTypeRequiredDetails
base_revisionintegerYesLast known note revision. format: "int64" minimum: 1

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

204 Deleted; empty body.
400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

404 note_not_found or staged_note_not_found.

Content-Type: application/json

Schema: Error

409 note_revision_conflict

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

503 Pending file recovery prevents writes. Retain operations and retry with backoff after recovery; code depends on route (note_file_recovery_blocked, note_recovery_pending, note_recovery_failed).

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Search plaintext titles and tags

GET /api/search

Title/tag prefix search, not body search. Uses at most the first eight whitespace-separated terms, combined with AND; returns up to 50 ranked summaries. An empty query returns []; no matches for a nonempty query can return null. Encrypted mode returns 409 client_search_required; decrypt and search locally.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

None.

Query parameters

ParameterTypeRequiredDetails
qstringNoTrimmed search text, at most 256 UTF-8 bytes.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Ranked plaintext note summaries or null.

Content-Type: application/json

Schema: array / null

Type: array / null

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 client_search_required: decrypt summaries and search/filter/index tags locally.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

List plaintext tags

GET /api/tags

Encrypted mode returns 409 client_search_required. Build its tag index from decrypted summaries locally.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Distinct tags.

Content-Type: application/json

Schema: array of string

Type: array of string

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 client_search_required: decrypt summaries and search/filter/index tags locally.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Read the durable note change feed

GET /api/sync

since is exclusive. Store nextSequence only after committing the fetched changes locally. Follow hasMore; when false, stop fetching until a new hint/poll. resetRequired means the retained feed no longer covers the cursor: rebuild a snapshot without deleting pending edits. instance_id distinguishes replacement databases; do not infer identity from a cursor. See the protocol guide for snapshot races and reset handling.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

None.

Query parameters

ParameterTypeRequiredDetails
sinceintegerNo default: 0 format: "int64" minimum: 0
limitintegerNo default: 100 minimum: 1 maximum: 500

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Ordered changes, tombstones, cursor and server identity.

Content-Type: application/json

Schema: ChangesPage

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Fetch complete notes in a small batch

GET /api/sync/notes

Up to 25 comma-separated IDs, counted before deduplication. Empty ids returns empty notes/missing arrays. A fetched revision may be newer than the change-feed entry; do not overwrite pending local edits.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"

Path parameters

None.

Query parameters

ParameterTypeRequiredDetails
idsstringNoComma-separated note IDs; no spaces.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Existing complete notes and missing IDs.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
notesarray of CompleteNoteYes
missingarray of IDYes
400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Apply ordered offline operations

POST /api/sync/push

At most 100 operations; 4 MiB plaintext or 8 MiB encrypted JSON. Empty operations is allowed to read expected_sequence. Full-note saves, not text deltas. Sequence starts at 1 and is contiguous per device_id; preserve it and stable op_id across retries. Conflicts consume sequence numbers. A 409 sequence gap can acknowledge an already-committed prefix. Timeouts/5xx can also follow partial commits: retry the identical operations. Do not interpret every HTTP 200 as every operation applied. See the protocol guide.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
X-Vylk-Vault-ProtocolstringNoRequired value 1 for this route in encrypted mode; otherwise 426. Not required for authentication, preferences, keys, migration, or SSE. const: "1"
X-Vylk-Instance-IDstringNoStrongly recommended: persisted server instance_id. A mismatch returns 409 before any writes. Omission is accepted for compatibility, not safe for offline replacement-database recovery.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: PushRequest or EncryptedPushRequest

Variants:

{
  "device_id": "native-client-queue",
  "operations": [
    {
      "client_sequence": 1,
      "op_id": "save-first-note",
      "type": "note.save",
      "note_id": "my-note",
      "base_revision": 0,
      "title": "First note",
      "tags": "ideas",
      "content": "Hello"
    }
  ]
}

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Inspect each acknowledgement status.

Content-Type: application/json

Schema: PushResult

400 invalid_sync_request, invalid_sync_operation or invalid_encrypted_operation. Plaintext invalid_sync_operation includes permanent=true, operation_index, client_sequence and op_id.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 Sequence gap (PushResult), invalid_replayed_operation, server_instance_changed, or stale_vault_epoch (Error, possibly epoch). A changed server requires a user decision; never silently renumber/re-upload pending edits.

Content-Type: application/json

Schema: PushResult or Error

Variants:

423 vault_migration_in_progress (writes during preparation), vault_maintenance_in_progress (cleaning), or vault_unavailable. Pause writes; do not discard pending changes.

Content-Type: application/json

Schema: Error

426 encrypted_client_required: set X-Vylk-Vault-Protocol: 1 and implement the encrypted contract; never retry a plaintext body as encrypted.

Content-Type: application/json

Schema: Error

503 Pending file recovery prevents writes. Retain operations and retry with backoff after recovery; code depends on route (note_file_recovery_blocked, note_recovery_pending, note_recovery_failed).

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Subscribe to change and session hints

GET /api/events

Server-Sent Events, not a durable feed. Starts with retry: 3000, server and heartbeat events; heartbeats every 25 seconds. change events hint notes/preferences; session-expired closes the connection. No event IDs or Last-Event-ID replay. Reconnect with cookies and perform a durable pull after hints/reconnect. Plain-text 500 is possible before stream setup.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Cache-Control200 const: "no-cache"
X-Accel-Buffering200 const: "no"

Responses

200 A long-lived UTF-8 SSE stream.

Content-Type: text/event-stream

Schema: string

Type: string

retry: 3000

event: server
data: {"version":"3.0.4","revision":"asset-revision"}

event: change
data: {"type":"notes","sequence":1}

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

500 Stream setup failed.

Content-Type: text/plain

Schema: string

Type: string

Read account preferences

GET /api/prefs

Preferences are not encrypted. Read revision from JSON; no ETag is emitted.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Current preferences with normalized defaults.

Content-Type: application/json

Schema: Preferences

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Replace account preferences

PATCH /api/prefs

Despite PATCH, this is full replacement, not a partial merge. Read, modify, and send the complete object with If-Match. Missing fields can reset settings. Limit: 16 KiB JSON. _sync_patch/_sync_base only implement partial merging in sync prefs.save; this endpoint ignores those helpers.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
If-MatchstringNoPositive revision, plain or quoted. Missing or * permits unconditional replacement; avoid this in multi-device clients.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: Preferences

Account-wide unencrypted UI settings. For sync partial updates, supply _sync_patch (desired fields) and _sync_base (previous values of those fields) with the operation base_revision; stale changes merge only when each current field equals the base or desired value. Defaults documented here apply to a fresh account, not every omitted field in PATCH.

FieldTypeRequiredDetails
revisionintegerNoformat: "int64"
autoSavebooleanNodefault: true
reduceMotionstringNoSystem follows the OS accessibility setting; Always disables interface animations; Never explicitly enables them regardless of the OS setting. enum: ["system", "always", "never"] default: "system"
startViewstringNoenum: ["editor", "preview", "split", "zen"] default: "editor"
hideToolbarbooleanNodefault: false
hideSaveButtonbooleanNodefault: true
saveButtonLocationstringNoenum: ["", "panel", "header"] default: "panel"
collapseDetailsbooleanNo
hideCursorHighlightbooleanNo
interactivePreviewbooleanNodefault: true
statusDisplaystringNo
contentWidthContentWidthNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty width. enum: ["", "compact", "standard", "wide", "full"]
zenPageWidthContentWidthNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty width. enum: ["", "compact", "standard", "wide", "full"]
themestringNoBuilt-in theme name or client-defined palette string; server stores it without interpreting the palette.
accentColorstringNo
fontFamilystringNo
fontFamilyGooglebooleanNo
fontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
editorFontFamilystringNo
editorFontFamilyGooglebooleanNo
editorFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
previewFontFamilystringNo
previewFontFamilyGooglebooleanNo
previewFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
zenFontFamilystringNodefault: "Inter"
zenFontFamilyGooglebooleanNodefault: true
zenFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"] default: "1.25rem"
zenWordCountbooleanNodefault: true
zenShowTitlebooleanNodefault: true
zenShowControlsbooleanNodefault: true
zenInteractivePreviewbooleanNo
shortcutPrefixShortcutBindingNoExactly one step; default Mod+E (Ctrl+E on Windows/Linux, Command+E on macOS).
keyboardShortcutsobjectNomaxProperties: 64
shortcutConfirmationSkipsobjectNomaxProperties: 64
_sync_patchobjectNo
_sync_baseobjectNo

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Saved preferences with new revision.

Content-Type: application/json

Schema: Preferences

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 preferences_revision_conflict, with current_revision.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Discover vault mode and derivation settings

GET /api/vault/bootstrap

Public, no key wrappers. Compare instance_id with the local cache identity before reading notes or restoring remembered keys. In legacy mode vault_id, epoch, and kdf are absent. reset_available does not override VYLK_DISABLE_VAULT_CHANGES; the reset API can still reject the operation.

Request headers

None.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Cache-Control200 const: "no-store"

Responses

200 Current encryption mode and public KDF settings.

Content-Type: application/json

Schema: Bootstrap

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Read encrypted vault-key wrappers

GET /api/vault/keys

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Decrypt locally using the appropriate derived wrapping key.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
epochintegerYesformat: "int64"
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 vault_not_encrypted

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Change passphrase or recovery credentials

POST /api/vault/credentials

Limit: 32 KiB. Rewrap the SAME vault key; do not replace it. master requires fresh KDF parameters and updates the login proof; recovery updates the recovery proof/wrapper. current_recovery chooses which old proof authorizes the action, independent of kind. Always revokes the current session; sign_out_other_devices defaults true for either kind. Success requires a new login. Does not rotate the note key, vault ID, or epoch.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: CredentialChange

FieldTypeRequiredDetails
kindstringYesenum: ["master", "recovery"]
current_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
current_recoverybooleanNodefault: false
new_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
kdf_saltVaultIDNoRequired for master changes. minLength: 22 maxLength: 22
kdf_memory_kibintegerNoRequired for master changes. minimum: 19456 maximum: 262144
kdf_iterationsintegerNoRequired for master changes. minimum: 2 maximum: 10
sign_out_other_devicesbooleanNodefault: true

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Set-Cookie200May contain multiple Set-Cookie lines. session is HttpOnly, Path=/, SameSite=Lax, 180-day lifetime; vylk-device groups browser-profile sessions and lasts 365 days. Secure is set for HTTPS. Authenticated activity periodically renews session. Never combine Set-Cookie lines with commas.

Responses

200 Credentials changed; session cookie cleared.

Content-Type: application/json

Schema: OK

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

409 vault_unavailable

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Start a new empty vault (destructive)

POST /api/vault/reset

Optional recovery-from-total-key-loss operation, NOT passphrase recovery or decryption. Requires configured VYLK_PASSWORD in the body, not a session/proof. Limit: 32 KiB. Archives the old database/ciphertext for 30 days, then replaces credentials, clears notes/feed/queue counters and revokes all sessions. Returns a new session. The old data still needs its old key. instance_id remains unchanged; vault_id changes and epoch increments. Confirm data-loss consequences before calling. Do not retry an ambiguous reset blindly.

Request headers

HeaderTypeRequiredDetails
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: VaultReset

FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_saltVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_memory_kibintegerYesminimum: 19456 maximum: 262144
kdf_iterationsintegerYesminimum: 2 maximum: 10
auth_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
passwordstringYesConfigured VYLK_PASSWORD, not vault passphrase.

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

HeaderStatusDetails
Set-Cookie200May contain multiple Set-Cookie lines. session is HttpOnly, Path=/, SameSite=Lax, 180-day lifetime; vylk-device groups browser-profile sessions and lasts 365 days. Secure is set for HTTPS. Authenticated activity periodically renews session. Never combine Set-Cookie lines with commas.
Retry-After429Delay in seconds.

Responses

200 New empty vault and new session.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
okbooleanYesconst: true
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
epochintegerYesformat: "int64"
archive_expires_atstringYesformat: "date-time"
400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled or vault_reset_unavailable (no server password).

Content-Type: application/json

Schema: Error

409 vault_reset_unavailable (not an active encrypted vault).

Content-Type: application/json

Schema: Error

429 login_rate_limited. Wait retry_after seconds; do not hammer credentials.

Content-Type: application/json

Schema: Error

503 Pending file recovery prevents writes. Retain operations and retry with backoff after recovery; code depends on route (note_file_recovery_blocked, note_recovery_pending, note_recovery_failed).

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Prepare plaintext-to-encrypted migration

POST /api/vault/migration/start

Limit: 32 KiB. Requires old_password even with a session. Generate a random vault key/ID and both credential wrappers locally. Creates a consistent backup and pauses plaintext writes. Repeating the same vault_id resumes setup; it does not replace previously stored credentials. Preserve the original setup secrets until commit. No abort or disable-encryption API exists.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: MigrationStart

FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_saltVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_memory_kibintegerYesminimum: 19456 maximum: 262144
kdf_iterationsintegerYesminimum: 2 maximum: 10
auth_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
old_passwordstringYes

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Migration prepared.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
okbooleanYesconst: true
epochintegerYesconst: 1
400 invalid_vault_setup, including incorrect old_password.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

409 vault_setup_already_started or unexpected_note_files

Content-Type: application/json

Schema: Error

503 Pending file recovery prevents writes. Retain operations and retry with backoff after recovery; code depends on route (note_file_recovery_blocked, note_recovery_pending, note_recovery_failed).

Content-Type: application/json

Schema: Error

507 migration_backup_failed: free storage and retry same setup.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Read encryption migration progress

GET /api/vault/migration/status

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Progress counts and mode.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
modeVaultModeYesenum: ["legacy", "preparing", "cleaning", "encrypted"]
backup_readybooleanYes
source_notesintegerYes
staged_notesintegerYes
verified_notesintegerYes
401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 Migration state/source mismatch: vault_migration_not_started, vault_migration_not_ready, vault_note_changed, vault_verify_mismatch, vault_migration_incomplete, or unexpected_note_files. Inspect state before retrying.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

List notes still needing verification

GET /api/vault/migration/next

Up to 100 notes in lexicographic ID order. Empty array means no remaining unverified notes. A source revision change invalidates its previous verification.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

ParameterTypeRequiredDetails
afterstringNoLast ID from the preceding page, exclusive. This is an ID, not a notes-page cursor.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Source IDs and revisions.

Content-Type: application/json

Schema: array of object

Type: array of object

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

409 Migration state/source mismatch: vault_migration_not_started, vault_migration_not_ready, vault_note_changed, vault_verify_mismatch, vault_migration_incomplete, or unexpected_note_files. Inspect state before retrying.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Stage an encrypted copy of a source note

POST /api/vault/migration/stage

Limit: 8 MiB. Source revision must match. Restaging clears its verification. Fetch and decrypt the staged copy before verifying it.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: StagedRequest

FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
revisionintegerYesformat: "int64" minimum: 1
summaryEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
bodyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Completed.

Content-Type: application/json

Schema: OK

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

409 Migration state/source mismatch: vault_migration_not_started, vault_migration_not_ready, vault_note_changed, vault_verify_mismatch, vault_migration_incomplete, or unexpected_note_files. Inspect state before retrying.

Content-Type: application/json

Schema: Error

507 vault_stage_failed: insufficient storage.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Read a staged encrypted note

GET /api/vault/migration/staged/{id}

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

ParameterTypeRequiredDetails
idIDYesNote identifier. pattern: "^[A-Za-z0-9_-]{1,64}$"

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Staged copy. body_hash is SHA-256 of exact stored envelope JSON bytes, not decrypted Markdown or reserialized JSON.

Content-Type: application/json

Schema: StagedNote

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

404 note_not_found or staged_note_not_found.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Acknowledge client verification of a staged note

POST /api/vault/migration/verify

Limit: 4 KiB. First decrypt the downloaded staged summary/body and compare them with the source. The server only checks the stored hash and source revision; it cannot verify decryption. Send the body_hash returned by staged/{id}.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.
Content-TypestringYesapplication/json

Path parameters

None.

Query parameters

None.

Request body

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
body_hashstringYesSHA-256, lowercase hexadecimal. minLength: 64 maxLength: 64

Unknown fields are rejected.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Completed.

Content-Type: application/json

Schema: OK

400 Malformed, oversized, unknown-field, or invalid request (invalid_request or a route-specific code). JSON body limits return 400, not 413.

Content-Type: application/json

Schema: Error

401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

409 Migration state/source mismatch: vault_migration_not_started, vault_migration_not_ready, vault_note_changed, vault_verify_mismatch, vault_migration_incomplete, or unexpected_note_files. Inspect state before retrying.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Commit verified encrypted notes and remove plaintext

POST /api/vault/migration/commit

No body required. Requires backup and all source notes verified. Revokes all sessions; log in with the derived proof afterward. Note queue counters survive migration; do not restart sequence at 1. If vault_cleanup_pending returns 500, encrypted cutover already succeeded; cleanup resumes on server restart. Do not revert to plaintext or start a new setup.

Request headers

HeaderTypeRequiredDetails
CookiestringYessession=<token>; use a persistent cookie jar.

Path parameters

None.

Query parameters

None.

Request body

None.

Response headers

Content-Type is shown with each response below; 204 has no body. Any authenticated response may renew the session cookie. Infrastructure may add transport/security headers.

No additional documented response headers.

Responses

200 Encrypted cutover completed.

Content-Type: application/json

Schema: object

FieldTypeRequiredDetails
okbooleanYesconst: true
modestringYesconst: "encrypted"
401 authentication_required for a missing, expired, or revoked session; credential endpoints can return invalid_credentials.

Content-Type: application/json

Schema: Error

403 vault_changes_disabled: server policy rejects vault/device mutations. Ordinary notes and preferences remain usable.

Content-Type: application/json

Schema: Error

409 Migration state/source mismatch: vault_migration_not_started, vault_migration_not_ready, vault_note_changed, vault_verify_mismatch, vault_migration_incomplete, or unexpected_note_files. Inspect state before retrying.

Content-Type: application/json

Schema: Error

default Storage/internal failure with a route-specific code. Some writes may already have committed; reconcile rather than assuming nothing changed.

Content-Type: application/json

Schema: Error

Wire models

ID

pattern: "^[A-Za-z0-9_-]{1,64}$"

Type: string

QueueID

pattern: "^[A-Za-z0-9_-]{1,128}$"

Type: string

VaultID

16 random bytes, unpadded strict base64url (22 characters).

16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22

Type: string

Proof

32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead.

32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43

Type: string

Envelope

AES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.

FieldTypeRequiredDetails
vintegerYesconst: 1
noncestringYesminLength: 16 maxLength: 16
ciphertextstringYesminLength: 22
Error
FieldTypeRequiredDetails
errorstringYesHuman-readable message; branch on code instead.
codestringYes
note_idstringNo
current_revisionintegerNoformat: "int64"
epochintegerNoformat: "int64"
retry_afterintegerNo
permanentbooleanNo
operation_indexintegerNominimum: 0
client_sequenceintegerNoformat: "int64"
op_idstringNo
OK
FieldTypeRequiredDetails
okbooleanYesconst: true
Login
FieldTypeRequiredDetails
passwordstringNoServer password, legacy/preparing only.
proofProofNo32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofNo32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43

Unknown fields are rejected.

LoginResult
FieldTypeRequiredDetails
okbooleanYesconst: true
versionstringYes
revisionstringYesApplication asset revision.
instance_idstringYesPersistent database identity.
CheckResult
FieldTypeRequiredDetails
okbooleanYesconst: true
versionstringYes
revisionstringYesApplication asset revision.
instance_idstringYesPersistent database identity.
vault_modeVaultModeYesenum: ["legacy", "preparing", "cleaning", "encrypted"]
vault_epochintegerYesformat: "int64"
Device
FieldTypeRequiredDetails
idstringYes
namestringYes
last_seen_atstringYesformat: "date-time"
currentbooleanYes
PlainNote
FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
titlestringYesAt most 512 UTF-8 bytes on save.
filenamestringYesStorage name; not an API path.
tagsstringYesComma-separated, trimmed and deduplicated, case-sensitive; at most 4096 UTF-8 bytes in a save request.
pinnedbooleanYes
pin_orderintegerNoMay be absent when zero. format: "int64"
created_atstringYesformat: "date-time"
updated_atstringYesformat: "date-time"
revisionintegerYesformat: "int64"
EncryptedNote
FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
summaryEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
pinnedbooleanYes
pin_orderintegerYesformat: "int64"
created_atstringYesformat: "date-time"
updated_atstringYesformat: "date-time"
revisionintegerYesformat: "int64"
epochintegerYesformat: "int64"
NoteSummary

Variants:

CompleteNote

Variants:

NotesPage
FieldTypeRequiredDetails
notesarray of NoteSummaryYes
nextCursorstringNoOpaque cursor; absent/empty at end.
SaveNote
FieldTypeRequiredDetails
idIDNopattern: "^[A-Za-z0-9_-]{1,64}$"
titlestringNo
contentstringNo
tagsstringNo
base_revisionintegerNoRequired when id is supplied; 0 creates a new client-generated ID. format: "int64"

Unknown fields are rejected.

SaveEncryptedNote
FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
summaryEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
bodyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
epochintegerYesformat: "int64" minimum: 1
base_revisionintegerYesformat: "int64"

Unknown fields are rejected.

Change
FieldTypeRequiredDetails
sequenceintegerYesformat: "int64"
note_idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
revisionintegerYesformat: "int64"
deletedbooleanYes
changed_atstringYesformat: "date-time"
ChangesPage
FieldTypeRequiredDetails
changesarray of ChangeYes
nextSequenceintegerYesformat: "int64"
hasMorebooleanYes
resetRequiredbooleanNo
instance_idstringYes
Operation

Conditional fields: save needs note_id/base_revision and complete title/tags/content; delete needs note_id/base_revision >= 1; pin needs note_id/base_revision >= 0/pinned; prefs.save needs prefs and preferably base_revision; noop only needs sequence/op_id/type. base_content is accepted but does not cause server-side text merging. Existing note pin state changes through note.pin.

FieldTypeRequiredDetails
client_sequenceintegerYesformat: "int64" minimum: 1
op_idQueueIDYespattern: "^[A-Za-z0-9_-]{1,128}$"
typestringYesenum: ["note.save", "note.delete", "note.pin", "prefs.save", "noop"]
note_idIDNopattern: "^[A-Za-z0-9_-]{1,64}$"
base_revisionintegerNoformat: "int64"
titlestringNo
tagsstringNo
contentstringNo
base_contentstringNo
pinnedbooleanNo
prefsPreferencesNoAccount-wide unencrypted UI settings. For sync partial updates, supply _sync_patch (desired fields) and _sync_base (previous values of those fields) with the operation base_revision; stale changes merge only when each current field equals the base or desired value. Defaults documented here apply to a fresh account, not every omitted field in PATCH.

Unknown fields are rejected.

EncryptedOperation

save requires note_id/base_revision/summary/body/epoch; delete and pin require note_id/base_revision/epoch (and pinned for pin). prefs.save and noop do not require epoch. Plaintext title/tags/content/base_content fields are rejected. Envelope AAD binds epoch; do not change it without re-encrypting.

FieldTypeRequiredDetails
client_sequenceintegerYesformat: "int64" minimum: 1
op_idQueueIDYespattern: "^[A-Za-z0-9_-]{1,128}$"
typestringYesenum: ["note.save", "note.delete", "note.pin", "prefs.save", "noop"]
note_idIDNopattern: "^[A-Za-z0-9_-]{1,64}$"
base_revisionintegerNoformat: "int64"
summaryEnvelopeNoAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
bodyEnvelopeNoAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
epochintegerNoformat: "int64" minimum: 1
pinnedbooleanNo
prefsPreferencesNoAccount-wide unencrypted UI settings. For sync partial updates, supply _sync_patch (desired fields) and _sync_base (previous values of those fields) with the operation base_revision; stale changes merge only when each current field equals the base or desired value. Defaults documented here apply to a fresh account, not every omitted field in PATCH.

Unknown fields are rejected.

PushRequest
FieldTypeRequiredDetails
device_idQueueIDYespattern: "^[A-Za-z0-9_-]{1,128}$"
operationsarray of OperationYesmaxItems: 100

Unknown fields are rejected.

EncryptedPushRequest
FieldTypeRequiredDetails
device_idQueueIDYespattern: "^[A-Za-z0-9_-]{1,128}$"
operationsarray of EncryptedOperationYesmaxItems: 100

Unknown fields are rejected.

Acknowledgement
FieldTypeRequiredDetails
client_sequenceintegerYesformat: "int64"
op_idstringYes
statusstringYesAll consume sequence. compacted acknowledges an old consumed operation whose detailed result was pruned, not proof that remote content matches. enum: ["applied", "conflict", "compacted"]
revisionintegerNoformat: "int64"
current_revisionintegerNoformat: "int64"
pin_orderintegerNoformat: "int64"
PushResult
FieldTypeRequiredDetails
acknowledgedarray of AcknowledgementYes
expected_sequenceintegerYesformat: "int64" minimum: 1
ShortcutStep

First step modifiers are [Mod] or [Mod, Shift]; second step modifiers must be empty.

FieldTypeRequiredDetails
keystringYespattern: "^[A-Za-z0-9/;]$"
modifiersarray of stringYes

Unknown fields are rejected.

ShortcutBinding
FieldTypeRequiredDetails
stepsarray of ShortcutStepYesminItems: 1 maxItems: 2

Unknown fields are rejected.

FontSize

Empty is accepted in full replacement; a sync patch must use a listed nonempty size.

Empty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]

Type: string

ContentWidth

Empty is accepted in full replacement; a sync patch must use a listed nonempty width.

Empty is accepted in full replacement; a sync patch must use a listed nonempty width. enum: ["", "compact", "standard", "wide", "full"]

Type: string

Preferences

Account-wide unencrypted UI settings. For sync partial updates, supply _sync_patch (desired fields) and _sync_base (previous values of those fields) with the operation base_revision; stale changes merge only when each current field equals the base or desired value. Defaults documented here apply to a fresh account, not every omitted field in PATCH.

FieldTypeRequiredDetails
revisionintegerNoformat: "int64"
autoSavebooleanNodefault: true
reduceMotionstringNoSystem follows the OS accessibility setting; Always disables interface animations; Never explicitly enables them regardless of the OS setting. enum: ["system", "always", "never"] default: "system"
startViewstringNoenum: ["editor", "preview", "split", "zen"] default: "editor"
hideToolbarbooleanNodefault: false
hideSaveButtonbooleanNodefault: true
saveButtonLocationstringNoenum: ["", "panel", "header"] default: "panel"
collapseDetailsbooleanNo
hideCursorHighlightbooleanNo
interactivePreviewbooleanNodefault: true
statusDisplaystringNo
contentWidthContentWidthNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty width. enum: ["", "compact", "standard", "wide", "full"]
zenPageWidthContentWidthNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty width. enum: ["", "compact", "standard", "wide", "full"]
themestringNoBuilt-in theme name or client-defined palette string; server stores it without interpreting the palette.
accentColorstringNo
fontFamilystringNo
fontFamilyGooglebooleanNo
fontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
editorFontFamilystringNo
editorFontFamilyGooglebooleanNo
editorFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
previewFontFamilystringNo
previewFontFamilyGooglebooleanNo
previewFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"]
zenFontFamilystringNodefault: "Inter"
zenFontFamilyGooglebooleanNodefault: true
zenFontSizeFontSizeNoEmpty is accepted in full replacement; a sync patch must use a listed nonempty size. enum: ["", "0.8rem", "0.9rem", "1rem", "1.1rem", "1.25rem", "1.5rem"] default: "1.25rem"
zenWordCountbooleanNodefault: true
zenShowTitlebooleanNodefault: true
zenShowControlsbooleanNodefault: true
zenInteractivePreviewbooleanNo
shortcutPrefixShortcutBindingNoExactly one step; default Mod+E (Ctrl+E on Windows/Linux, Command+E on macOS).
keyboardShortcutsobjectNomaxProperties: 64
shortcutConfirmationSkipsobjectNomaxProperties: 64
_sync_patchobjectNo
_sync_baseobjectNo

Unknown fields are rejected.

VaultMode

enum: ["legacy", "preparing", "cleaning", "encrypted"]

Type: string

Bootstrap
FieldTypeRequiredDetails
modeVaultModeYesenum: ["legacy", "preparing", "cleaning", "encrypted"]
reset_availablebooleanYes
instance_idstringYesPersistent database identity. Compare before reading cached notes or resuming sync; a restart with the same database preserves it.
require_strong_passwordsbooleanYes
versionstringYes
revisionstringYes
vault_idVaultIDNo16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
epochintegerNoformat: "int64"
kdfobjectNo
VaultSetup
FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_saltVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_memory_kibintegerYesminimum: 19456 maximum: 262144
kdf_iterationsintegerYesminimum: 2 maximum: 10
auth_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
MigrationStart
FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_saltVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_memory_kibintegerYesminimum: 19456 maximum: 262144
kdf_iterationsintegerYesminimum: 2 maximum: 10
auth_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
old_passwordstringYes

Unknown fields are rejected.

VaultReset
FieldTypeRequiredDetails
vault_idVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_saltVaultIDYes16 random bytes, unpadded strict base64url (22 characters). minLength: 22 maxLength: 22
kdf_memory_kibintegerYesminimum: 19456 maximum: 262144
kdf_iterationsintegerYesminimum: 2 maximum: 10
auth_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
recovery_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
wrapped_recovery_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
passwordstringYesConfigured VYLK_PASSWORD, not vault passphrase.

Unknown fields are rejected.

CredentialChange
FieldTypeRequiredDetails
kindstringYesenum: ["master", "recovery"]
current_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
current_recoverybooleanNodefault: false
new_proofProofYes32 derived bytes, unpadded strict base64url (43 characters). Reusable authentication secret; never log it or send SHA-256(proof) instead. minLength: 43 maxLength: 43
wrapped_keyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
kdf_saltVaultIDNoRequired for master changes. minLength: 22 maxLength: 22
kdf_memory_kibintegerNoRequired for master changes. minimum: 19456 maximum: 262144
kdf_iterationsintegerNoRequired for master changes. minimum: 2 maximum: 10
sign_out_other_devicesbooleanNodefault: true

Unknown fields are rejected.

StagedRequest
FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
revisionintegerYesformat: "int64" minimum: 1
summaryEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
bodyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.

Unknown fields are rejected.

StagedNote
FieldTypeRequiredDetails
idIDYespattern: "^[A-Za-z0-9_-]{1,64}$"
revisionintegerYesformat: "int64"
summaryEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
bodyEnvelopeYesAES-256-GCM envelope, an object rather than a JSON-encoded string. Nonce: 12 bytes. Ciphertext includes the final 16-byte authentication tag. Both are strict unpadded base64url. Summary envelope JSON is limited to 37,888 bytes.
epochintegerYesformat: "int64"
body_hashstringYes