Skip to content

Desktop API reference

The REST endpoints the AUTO/PILOT desktop app uses for auth, activation and updates.

All endpoints live under /api/v1, speak JSON, and return

{ "ok": true,  ...payload }
{ "ok": false, "error": { "code": "ERROR_CODE", "message": "Human readable" } }

Authenticated calls use Authorization: Bearer <sessionToken>. Cookies are not accepted on this surface, which is what keeps it free of CSRF concerns.

Error codes

Code HTTP Meaning
VALIDATION_FAILED 422 Body failed schema validation; see issues.
INVALID_CREDENTIALS 401 Wrong email or password.
EMAIL_NOT_VERIFIED 403 Confirm the address on the website first.
ACCOUNT_DISABLED 403 Account disabled by an administrator.
UNAUTHORIZED 401 Missing, invalid or expired bearer token.
LICENSE_REQUIRED 403 No non-revoked license pack on the account.
ACTIVATION_LIMIT_REACHED 409 All slots in use. Free one or buy a pack.
DEVICE_DEACTIVATED 403 This activation was released; re-activate.
DEVICE_CLAIMED_BY_OTHER_ACCOUNT 409 Device token belongs to another account.
RATE_LIMITED 429 Back off; see retryAfterSeconds.
SERVER_MISCONFIGURED 503 Server is missing keys or storage config.

POST /api/v1/auth/login

Signs in and, when deviceToken and deviceName are present, activates the device in the same call.

{
  "email": "dj@example.com",
  "password": "…",
  "deviceName": "booth-mbp",
  "deviceToken": "9f3c…opaque-32-bytes-or-more",
  "platform": "mac",
  "appVersion": "1.0.3"
}

Response:

{
  "ok": true,
  "sessionToken": "…",
  "sessionExpiresAt": "2026-03-01T12:00:00.000Z",
  "licenseToken": "eyJhbGciOiJSUzI1NiIs…",
  "licenseTokenExpiresAt": "2026-01-30T12:00:00.000Z",
  "user": { "id": "…", "email": "dj@example.com", "role": "user", "emailVerified": true },
  "entitlement": {
    "licensed": true,
    "packs": 1,
    "maxActivations": 3,
    "activeActivations": 1,
    "remainingActivations": 2
  },
  "device": { "id": "…", "name": "booth-mbp", "active": true }
}

Generate deviceToken once, locally, from a CSPRNG (32 bytes, URL-safe) and store it in the app data directory. Never derive it from a hardware id — the server only stores its SHA-256 hash and cannot recover it for you.

POST /api/v1/auth/refresh

Called during update checks. Returns a fresh licenseToken and the current entitlement. Rejects with LICENSE_REQUIRED, DEVICE_DEACTIVATED or ACCOUNT_DISABLED when the account state has changed since last time. Optional body:

{ "appVersion": "1.0.4", "deviceToken": "…" }

POST /api/v1/auth/logout

Ends the bearer session. The activation is not released — use the deactivate endpoint for that.

GET /api/v1/me

Account, role, entitlement, current device and tokenVersion.

GET /api/v1/devices

Every activation on the account. The one matching the calling session is flagged "current": true.

POST /api/v1/devices/activate

{ "deviceName": "backup-laptop", "deviceToken": "…", "platform": "windows", "appVersion": "1.0.3" }

Enforces the license and the activation limit, then binds the calling session to the device and returns a fresh license token.

POST /api/v1/devices/deactivate

{ "deviceId": "…" }

or

{ "deviceToken": "…" }

Frees the slot. If the deactivated device is the caller, sessionInvalidated is true and the app should discard its cached license token.

GET /api/v1/builds/latest

GET /api/v1/builds/latest?platform=mac&channel=stable

Returns build metadata plus a short-lived presigned download.url for licensed accounts. Unlicensed accounts get LICENSE_REQUIRED with the metadata attached but no URL.

GET /api/v1/license/public-key

Public verification material for offline license tokens — JWKS by default, PEM with ?format=pem. Pin it in the client; do not fetch it per verification.

Rate limits

Login and activation are rate limited per IP (and per account for activation). A 429 carries retryAfterSeconds — respect it with backoff rather than retrying in a loop.