Authentication

There are exactly two ways to authenticate: a session cookie for anything running in a browser, and a scoped API key for anything running on a server.

Session authentication (browser)

Signing in sets an HttpOnly, Secure, SameSite=Lax session cookie. Because the cookie travels automatically, every state-changing request must also carry a CSRF token. Fetch it once per page load:

bash
curl -i https://<your-api-host>/api/v1/ping --cookie-jar jar.txt
json
{ "ok": true, "csrf_token": "csrf_3f19...", "user": { "id": "usr_1", "role": "owner" } }

Send the token as X-CSRF-Token on every POST, PUT, PATCH and DELETE. From JavaScript, include credentials on every call:

ts
await fetch(`${API}/leads/lead_a1`, {
  method: "PATCH",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    "X-CSRF-Token": csrfToken,
    "X-Request-Id": crypto.randomUUID(),
  },
  body: JSON.stringify({ status: "working" }),
});
Cross-origin browser calls need the API to allow your exact origin with Access-Control-Allow-Credentials: true. If you would rather not manage that, proxy the API through your own server and keep the cookie same-origin.

API keys (server)

Keys look like cak_live_7Kd2… and are shown once, at creation. Send them as a bearer token:

bash
curl https://<your-api-host>/api/v1/opportunities \
  -H "Authorization: Bearer cak_live_7Kd2..."

Keys never need a CSRF token. Never ship a key to a browser, a mobile app, or a public repository — a leaked key can read every call in the workspace it belongs to.

Scopes

Each key carries an explicit list of scopes. Requests outside them return 403.

FieldTypeDescription
calls:readscopeList calls, read a call and its transcript.
opportunities:readscopeList and read scored acquisition opportunities.
opportunities:writescopeAssign an owner, change the outcome, add desk notes.
leads:readscopeList and read leads and contacts.
leads:writescopeCreate leads, update status and notes.
agent:readscopeRead the agent draft and the published configuration.
agent:writescopeReplace the draft and publish it.
webhooks:managescopeCreate, update and delete webhook endpoints.

Roles

Session users act within their role. A key can never exceed the role of the member who created it.

FieldTypeDescription
ownerroleEverything, including billing, deletion and transferring ownership.
adminroleAgent editing and publishing, team invitations, keys and webhooks.
memberroleWorks the desk: calls, opportunities, leads, follow-ups.
viewerroleRead-only across calls, opportunities and leads.

Tenancy

Every record belongs to one workspace. Requesting a record from another workspace returns 404 not_found, not 403, so existence cannot be inferred from status codes.