Skip to content

API overview

The Knobs API is a JSON REST API served from a single base URL:

https://api.knobs.io

Everything the Knobs apps can do — mail, chat, docs, sheets, drive, tasks, calendar, search — is available over this API. This page covers the conventions shared by every endpoint; the per-product pages document each surface.

  1. Mint an API key in the Knobs app under Settings → Security (or see API keys). The token looks like knak_<keyId>_<secret> and is shown exactly once.
  2. Call the API with the key as a bearer token:
Terminal window
curl -H "Authorization: Bearer knak_..." https://api.knobs.io/v1/users/me
{
"uid": "u_9f2c…",
"email": "you@yourteam.knobs.io",
"secondFactor": "",
"amr": ["apikey"],
"onboarding": ""
}
  1. Find your workspaces, then call workspace-scoped endpoints:
Terminal window
curl -H "Authorization: Bearer knak_..." https://api.knobs.io/v1/users/me/workspaces
curl -H "Authorization: Bearer knak_..." "https://api.knobs.io/v1/workspaces/{wid}/mail/threads?limit=10"

All routes live under /v1. Breaking changes would bump to /v2; additive fields are not breaking. The server tolerates unknown request fields, and your client should tolerate unknown response fields.

Requests and responses are JSON (application/json; charset=utf-8). File bytes never transit the API itself — Drive, doc assets, and mail attachments use short-lived signed Google Cloud Storage URLs for the actual bytes.

Every call (except the anonymous auth endpoints and the public calendar feed) requires Authorization: Bearer <token>. Two bearer types exist:

  • a session JWT, minted by the web/iOS clients at POST /v1/auth/exchange;
  • a personal access token (knak_…), for programmatic access.

See Authentication for which surface each reaches. A small set of credential-management routes are session-only and reject API keys.

Workspace-scoped resources live under /v1/workspaces/{wid}/<product>/…. The gateway resolves your membership before the handler runs:

  • non-members get 404 — the API never confirms that a workspace (or anything inside it) exists to someone who isn’t a member;
  • members without a sufficient role get 403.

The same existence-hiding applies at the object level: a doc, sheet, or calendar you have no access to reads as 404, never 403.

State transitions that aren’t plain CRUD use a :verb suffix on a collection path (…/mail/messages:send, …/tasks:create-from-mail) or a sub-path on an ID (…/tasks/{tid}/complete, …/domains/{did}/verify). All verb actions are POST.

List endpoints take ?pageSize= (default 50, max 200) and an opaque ?pageToken= (some surfaces use ?limit= and ?cursor= — noted per endpoint); responses return the items plus a next-page token/cursor. Pagination is cursor-based, never offset-based.

Mutating calls accept a clientMutationId body field (a UUID you generate); the service dedupes replays. Chat message sends require it. The bot API uses an Idempotency-Key header instead — see Integrations & webhooks.

Non-2xx responses return:

{
"error": {
"code": "PERMISSION_DENIED",
"message": "you do not have the required access level"
}
}

code is a stable UPPER_SNAKE enum; the HTTP status mirrors it. Canonical codes:

Code HTTP Meaning
INVALID_ARGUMENT 400 malformed or invalid request
UNAUTHENTICATED 401 missing/invalid bearer token
PERMISSION_DENIED 403 authenticated but not allowed
ONBOARDING_REQUIRED 403 session still mid-onboarding — finish onboarding in the app, then re-exchange
NOT_FOUND 404 missing, or exists but hidden from you
FAILED_PRECONDITION 409 valid request, wrong resource state
ABORTED 409 revision conflict — retry with fresh state
RESOURCE_EXHAUSTED 429 rate limit or quota; honor Retry-After
INTERNAL 500 server fault — safe to retry with backoff

See Errors & rate limits for details and 429 semantics.

Endpoint Description
GET /v1/users/me Who am I — uid, canonical email, auth method (amr), onboarding state
GET /v1/me Alias of GET /v1/users/me
GET /v1/users/me/workspaces List my workspaces
GET /v1/workspaces Alias of the workspace list

These identity reads accept API keys, so a programmatic client can always resolve who it is and where it can act.

Per-user (not workspace-scoped) endpoints for the in-app notification feed and push-device registration.

Endpoint Description
GET /v1/users/me/notifications In-app notification feed; ?unread=true and ?limit= supported
POST /v1/users/me/notifications/{nid}/read Mark one notification read
POST /v1/users/me/notifications/read-all Mark everything read → {"markedRead": n}
POST /v1/users/me/devices Register an FCM push token {platform, fcmToken, appVersion?, locale?, tz?} — platform is web, ios, or desktop
GET /v1/users/me/devices List registered devices
DELETE /v1/users/me/devices/{tid} Unregister a device token
Terminal window
curl -H "Authorization: Bearer knak_..." \
"https://api.knobs.io/v1/users/me/notifications?unread=true&limit=20"