Back to Help
Эта страница доступна только на английском языке.

API Documentation

Sav Account API v2 — the REST API that powers Notefox sync, accounts, and data management.

Overview

Base URL: https://notefox.eu/api/v2

The v2 API is a generic account platform called Sav Account. The account itself (signup, login, sessions, tokens, OTP, rate limiting) is shared and product-neutral. What is partitioned per service is only the synchronised data: every service owns its own snapshot and revision. Notefox is the first and default service.

  • Every endpoint is POST with a JSON body, except GET /status.
  • Content-Type: application/json
  • Body limited to 2 MB; the data field to 1.5 MB (413 beyond that).
  • Responses are always JSON with a real HTTP status.

Success response

{
  "status": "Successful",
  "code": 200,
  "data": { ... }
}

Error response

{
  "status": "Error",
  "code": 402,
  "description": "...",
  "data": null
}

Authentication

Most endpoints require a login-id and token, both 64 hexadecimal characters, obtained at login. Include them in the JSON body of every authenticated request.

Sessions can optionally have an expiry date. A session without expiry stays valid until an explicit logout or a password change.

Conventions

  • Dates — YYYY-MM-DD HH:MM:SS in the server timezone. GET /status returns server-time for clock-skew measurement.
  • Verification codes — 6 characters from 23456789ABCDEFGHJKLMNPQRSTUVWXYZ (no ambiguous characters), compared case-insensitively.
  • email in the payload — The server only stores the SHA-512 hash. The address is used exclusively as the email recipient and only if the hash matches the account; otherwise 410.
  • service in data endpoints — Optional, defaults to notefox. Must match ^[a-z0-9][a-z0-9_-]{0,31}$ and be declared in the server registry.
  • Sync history — A permission, not a feature. The data/get/history* endpoints require history-enabled = 1 on the account (default 0, no endpoint to change it).

Error codes

Code HTTP Meaning
200 200 OK
201 200 No data for this account
400 400 Missing or invalid parameters
401 503 Database unreachable
402 401 login-id missing, disabled, expired or invalid
403 401 Account not found
404 401 Token missing, disabled or expired
405 401 Invalid token
406 405 HTTP method not allowed
407 413 Payload too large
409 409 Revision conflict (response carries current data)
410 401 Invalid credentials
411 403 Account not active / not verified
412 400 Code expired
413 400 Invalid code
414 409 Account already verified
415 400 No code has been requested
419 409 Signup not completed
420 429 Too many wrong attempts, request a new code
429 429 Rate limit reached
430 409 Encryption key unavailable for this account
431 409 OTP is already in the requested state
432 409 Sync history not available for this service
433 403 Sync history not enabled for this account
434 409 History entry encrypted with a previous password
452 429 A deletion code has already been requested
500 500 Internal error (details in server log only)
503 503 Service temporarily unavailable

Status

GET /status

Health check endpoint. No authentication required.

Response

{
  "status": "Successful",
  "code": 200,
  "data": {
    "reachable": true,
    "database": true,
    "schema": true,
    "schema-details": {
      "keys": true,
      "snapshots": true,
      "snapshots-multi-service": true,
      "rate-limits": true,
      "otp": true,
      "otp-change-code": true,
      "password-change-code": true,
      "history-permission": true,
      "pro-features": true,
      "legacy-mirror": true
    },
    "mailer": true,
    "services": ["notefox"],
    "api-version": "2.0",
    "server-time": "2024-05-01 10:00:00",
    "server-timezone": "UTC"
  }
}
  • database boolean — Whether the DBMS is reachable.
  • schema boolean — Whether the required DDL has been applied.
  • schema-details object — Per-block DDL status. false means that piece is missing.
  • mailer boolean — Whether SMTP is available (no emails go out if false).
  • services array — Services configured on this installation.
  • server-time string — Current server time for clock-skew measurement.

Signup

POST /signup

Register a new account. The email verification code is always sent. The response is identical whether the address is free, already registered, or registered but never verified (prevents account enumeration).

Request body

{
  "username": "Sara",
  "password": "...",
  "email": "sara@example.com"
}
  • username string — Display name.
  • password string — Account password (min 8 characters).
  • email string — Email address (used as the identity root).

Response

{ "verification-required": true }

Errors

400 429

POST /signup/verify

Verify the signup email code. Activates the account, consumes the code, and creates the encryption key (DEK).

Request body

{
  "email": "...",
  "password": "...",
  "verification-code": "A1B2C3"
}
  • email string — The email used at signup.
  • password string — The account password.
  • verification-code string — The 6-character code received by email.

Response

{
  "verified": true,
  "username": "Sara",
  "encryption-ready": true
}

Errors

410 414 412 413 420

POST /signup/verify/get-new-code

Resend the signup verification code. Rate limited.

Request body

{
  "email": "...",
  "password": "..."
}
  • email string — The email used at signup.
  • password string — The account password.

Response

{ "verification-required": true }

Errors

410 429

Login

POST /login

Authenticate with email and password. If OTP is enabled (default), a verification code is emailed and must be confirmed via /login/verify. If OTP is disabled, the session is returned directly.

Request body

{
  "email": "...",
  "password": "..."
}
  • email string — Account email address.
  • password string — Account password.

Response (OTP enabled)

{
  "otp-required": true,
  "login-id": "...",
  "verification-expiry": "..."
}

Response (OTP disabled)

{
  "otp-required": false,
  "login-id": "...",
  "token": "...",
  "expiry": null,
  "username": "Sara",
  "encryption-ready": true
}

Errors

410 411 429

POST /login/verify

Complete login by providing the emailed verification code. The code is consumed and attempts are limited to 5.

Request body

{
  "login-id": "...",
  "email": "...",
  "password": "...",
  "verification-code": "A1B2C3"
}
  • login-id string — The login-id returned by /login.
  • email string — Account email address.
  • password string — Account password.
  • verification-code string — The 6-character code received by email.

Response

{
  "login-id": "...",
  "token": "...",
  "expiry": null,
  "username": "Sara",
  "encryption-ready": true
}

Errors

410 412 413 420

POST /login/verify/get-new-code

Resend the login verification code. Rate limited.

Request body

{
  "login-id": "...",
  "email": "...",
  "password": "..."
}
  • login-id string — The login-id returned by /login.
  • email string — Account email address.
  • password string — Account password.

Response

{
  "otp-required": true,
  "login-id": "...",
  "verification-expiry": "..."
}

POST /login/check-id

Check whether a session is still valid.

Request body

{
  "login-id": "...",
  "token": "..."
}
  • login-id string — Session identifier (64 hex chars).
  • token string — Session token (64 hex chars).

Response

{
  "valid": true,
  "username": "Sara",
  "expiry": null,
  "otp-enabled": true
}

Errors

402 404 405

POST /login/set-expiry

Set or remove the expiry date of a login session.

Request body

{
  "login-id": "...",
  "token": "...",
  "expiry": "2025-01-01 00:00:00"
}
  • expiry string|null — Date string or null to remove the expiry.

Response

{
  "login-id": "...",
  "expiry": "...",
  "old-expiry": null
}

POST /token/set-expiry

Set or remove the expiry date of the session token.

Request body

{
  "login-id": "...",
  "token": "...",
  "expiry": "2025-01-01 00:00:00"
}
  • expiry string|null — Date string or null to remove the expiry.

Response

{
  "expiry": "...",
  "old-expiry": null
}

POST /logout

End the current session or all sessions. The token is mandatory (in v1, knowing only the login-id was enough).

Request body

{
  "login-id": "...",
  "token": "...",
  "all-devices": false
}
  • all-devices boolean — If true, invalidates all sessions for the account.

Response

{
  "logged-out": true,
  "all-devices": false
}

Two-factor authentication (OTP)

OTP is enabled by default on every account. This setting affects only the login: it never disables the email verification required by signup, nor the confirmation code required to change the password or delete the account.

POST /otp/status

Check whether OTP is enabled for the authenticated account.

Request body

{
  "login-id": "...",
  "token": "..."
}

Response

{ "otp-enabled": true }

POST /otp/enable

Enable OTP for the account. Requires the password and sends a notification email.

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "email": "..."
}
  • password string — Account password.
  • email string optional — Used only to send the notification email; validated against the account hash.

Response

{ "otp-enabled": true }

Errors

431 (already enabled)

POST /otp/disable

Request to disable OTP. Requires a valid token and the password, then emails a confirmation code.

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "email": "..."
}
  • password string — Account password.
  • email string — Used to send the confirmation code; validated against the account hash.

Response

{
  "verification-required": true,
  "verification-expiry": "..."
}

Errors

431 (already disabled) 503

POST /otp/disable/verify

Confirm OTP disable with the emailed code. Single-use, with expiry and max 5 attempts.

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "verification-code": "A1B2C3",
  "email": "..."
}
  • password string — Account password.
  • verification-code string — The 6-character code received by email.
  • email string optional — Used to send the confirmation notification.

Response

{ "otp-enabled": false }

Errors

412 413 420 503

Sync & Data

All sync endpoints accept the optional service field (default notefox) and echo it back. An unknown or malformed service is refused with 400.

POST /data/insert

Write or update data for a service. Uses revision-based conflict detection.

Request body

{
  "login-id": "...",
  "token": "...",
  "service": "notefox",
  "data": "<json string>",
  "updated-locally": "2024-05-01 10:00:00",
  "base-revision": 41
}
  • service string optional, default "notefox" — Target service.
  • data string — JSON data to store (max 1.5 MB).
  • updated-locally string — Client-side timestamp. Must be a valid date, not in the future.
  • base-revision integer optional — The revision the client currently owns. Without it the write is always accepted (last-write-wins).

Success response

{
  "service": "notefox",
  "revision": 42,
  "updated-server": "2024-05-01 10:00:02",
  "updated-locally": "2024-05-01 10:00:00"
}

Conflict response (HTTP 409)

{
  "status": "Error",
  "code": 409,
  "description": "Revision conflict",
  "data": {
    "service": "notefox",
    "revision": 47,
    "updated-server": "...",
    "updated-locally": "...",
    "data": "<current json>"
  }
}

Errors

400 407 409 430

POST /data/get

Get the current data snapshot for a service.

Request body

{
  "login-id": "...",
  "token": "...",
  "service": "notefox"
}
  • service string optional, default "notefox" — Target service.

Response

{
  "service": "notefox",
  "data": "<json string>",
  "revision": 42,
  "updated-locally": "...",
  "updated-server": "..."
}

Errors

201 (no data) 430

POST /data/get/last-update

Get the revision and timestamps without decrypting the data. Token mandatory.

Request body

{
  "login-id": "...",
  "token": "...",
  "service": "notefox"
}
  • service string optional, default "notefox" — Target service.

Response

{
  "service": "notefox",
  "revision": 42,
  "updated-locally": "...",
  "updated-server": "..."
}

POST /data/services

List all services that hold data for the authenticated account.

Request body

{
  "login-id": "...",
  "token": "..."
}

Response

{
  "services": [
    {
      "service": "notefox",
      "revision": 42,
      "updated-server": "...",
      "updated-locally": "..."
    }
  ],
  "supported": ["notefox"],
  "history-enabled": false,
  "pro-features": false
}
  • services array — Services that have stored data for this account.
  • supported array — All services configured on this installation.
  • history-enabled boolean — Whether sync history is enabled for this account.
  • pro-features boolean — Whether premium features are enabled for this account.

POST /data/get/history

List past synced versions of a service (newest first, up to 30 entries). Requires history-enabled permission on the account.

Request body

{
  "login-id": "...",
  "token": "...",
  "service": "notefox"
}
  • service string optional, default "notefox" — Target service.

Response

{
  "service": "notefox",
  "entries": [
    {
      "id": 512,
      "inserted-date": "2024-05-01 10:00:02",
      "updated-locally-date": "2024-05-01 10:00:00"
    }
  ]
}

Errors

432 433

POST /data/get/history/download

Download a specific history entry by ID. The server decrypts before responding. Requires history-enabled permission.

Request body

{
  "login-id": "...",
  "token": "...",
  "service": "notefox",
  "id": 498
}
  • id integer — One of the IDs returned by /data/get/history.

Response

{
  "service": "notefox",
  "id": 498,
  "inserted-date": "2024-04-28 09:12:44",
  "updated-locally-date": "2024-04-28 09:12:40",
  "data": "<plaintext json>"
}

Errors

201 (not found) 432 433 434

Account management

Changing the password and deleting the account are critical operations: both always require the emailed confirmation code, regardless of the OTP setting.

POST /password/edit

Request a password change. Emails a confirmation code. Nothing is written until the code is verified.

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "new-password": "...",
  "email": "..."
}
  • new-password string optional here — At least 8 characters, different from current. If sent, rules are checked immediately (no email sent for an invalid change).

Response

{
  "verification-required": true,
  "verification-expiry": "..."
}

POST /password/edit/get-new-code

Resend the password change confirmation code. Rate limited (3 per 15 minutes).

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "email": "..."
}
  • password string — Current password.
  • email string — Account email address.

Response

{
  "verification-required": true,
  "verification-expiry": "..."
}

Errors

415 (no change requested) 429

POST /password/edit/verify

Complete the password change. Only the encryption key (DEK) is re-wrapped — no note is re-encrypted. All other sessions are invalidated and a new session is returned.

Request body

{
  "login-id": "...",
  "token": "...",
  "password": "...",
  "new-password": "...",
  "verification-code": "A1B2C3",
  "email": "..."
}
  • password string — Current password.
  • new-password string — New password (at least 8 characters, different from current).
  • verification-code string — Code received by email.
  • email string — Account email address.

Response

{
  "login-id": "<new>",
  "token": "<new>",
  "expiry": null
}

Errors

412 413 420

POST /delete

Request account deletion. Emails a confirmation code.

Request body

{
  "email": "...",
  "password": "..."
}
  • email string — Account email address.
  • password string — Current password.

Response

{
  "verification-required": true,
  "verification-expiry": "..."
}

Errors

452 (code already requested)

POST /delete/verify

Confirm account deletion. Permanently removes the account, all sessions, all data snapshots, encryption keys, and legacy data in one transaction.

Request body

{
  "email": "...",
  "password": "...",
  "deleting-code": "A1B2C3"
}
  • email string — Account email address.
  • password string — Current password.
  • deleting-code string — Confirmation code received by email.

Response

{ "deleted": true }

Errors

410 412 413 420

POST /delete/verify/get-new-code

Resend the account deletion confirmation code.

Request body

{
  "email": "...",
  "password": "..."
}
  • email string — Account email address.
  • password string — Current password.

Response

{
  "verification-required": true,
  "verification-expiry": "..."
}

Diagnostics

POST /error-logs/insert

Submit client error logs. No authentication required. Rate limited per IP. Length limits on every field.

Request body

{
  "datetime": "2024-05-01T10:00:00Z",
  "context": "popup.js:handleSync",
  "error": "TypeError: Cannot read property ...",
  "url": "https://example.com",
  "notefox-version": "4.6.0",
  "anonymous-userid": "a1b2c3..."
}
  • datetime string — Client date/time when the error occurred.
  • context string max 500 — Where the error happened (file, function, component).
  • error string max 65535 — Error message or stack trace.
  • url string optional, max 1000 — URL of the page where the error occurred.
  • notefox-version string optional, max 20 — Extension version.
  • anonymous-userid string optional, max 50 — Anonymous client identifier.

Response

{ "data": null }

POST /telemetry/insert

Submit client telemetry data. No authentication required. Rate limited per IP. Length limits on every field.

Request body

{
  "notefox-account": true,
  "anonymous-userid": "a1b2c3...",
  "client-datetime": "2024-05-01T10:00:00Z",
  "language": "en",
  "action": "sync",
  "context": "popup",
  "url": "https://example.com",
  "browser": "firefox",
  "browser-version": "126.0",
  "notefox-version": "4.6.0",
  "os": "macos",
  "other": ""
}
  • notefox-account boolean — Whether the user is signed in to a Notefox account.
  • anonymous-userid string max 50 — Anonymous client identifier.
  • client-datetime string — Client date/time of the event.
  • language string max 20 — Client language code.
  • action string max 500 — Action being tracked (e.g. "sync", "open-popup").
  • context string optional, max 500 — Additional context for the action.
  • url string optional, max 65535 — Page URL when the event fired.
  • browser string max 20 — Browser name.
  • browser-version string optional, max 20 — Browser version.
  • notefox-version string max 20 — Extension version.
  • os string optional, max 20 — Operating system.
  • other string optional, max 65535 — Any additional data.

Response

{ "data": null }

Rate limits

Rate limits protect the shared platform. Counters are per account, not per service. Beyond the limit: 429 with a temporary block.

Bucket Subject Limit
login email 10 / 15 min
login-ip IP 30 / 15 min
otp-verify login-id 5 / 15 min
otp-resend email or login-id 3 / 15 min
signup-ip / signup-email IP / email 10 and 5 per hour
signup-verify email 10 / 15 min
otp-change / otp-change-verify user 5–10 / 15 min
password-edit / password-edit-verify user 5 / 15 min
delete-request / delete-verify email 3 per hour / 5 per 15 min
data-insert user 120 / min
data-services user 60 / min
data-history user 60 / min
data-history-download user 30 / min
error-logs / telemetry IP 30 / 10 min

Encryption model

All user data is encrypted at rest. The server never stores the plaintext email (only its SHA-512 hash) or the plaintext password.

password ──PBKDF2(sha256, 210k, salt)──> KEK ──AES-256-CBC──> wrapped DEK  (user_keys)
DEK ──AES-256-CBC──> data, snapshot of every service                       (sav_data_current)
token ──AES-256-CBC──> password                                            (tokens, v1 format)
  • The DEK (Data Encryption Key) is generated once with random_bytes(32) and never changes. It encrypts all data across all services.
  • The password only ever encrypts the DEK. Changing the password re-wraps ~100 bytes — no note is re-encrypted.
  • key-check is the SHA-512 fingerprint of the DEK: it proves an unwrap produced the right key without storing the key.
  • The tokens table stores the password encrypted with the token (v1 compatibility), so a v2-issued token works on v1 endpoints.