Authentication
Every API request runs against api.saaslivery.com and must identify a workspace member. There are two credential types: workspace API keys for server-to-server integrations, and the session that signed-in users already carry in the browser.
Two headers are required on every request:
curl https://api.saaslivery.com/tasks \
-H "Authorization: Bearer slk_your_api_key_here" \
-H "X-Api-Version: 2026-03-15"
Requests with a body (POST, PUT, PATCH) must also send Content-Type: application/json, or the API responds with 415 Unsupported Media Type.
Workspace API keys
API keys are the credential for scripts, integrations, and anything that runs outside a browser. A key starts with the slk_ prefix and is passed as a Bearer token:
Authorization: Bearer slk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are created by workspace admins in Settings, under API keys. Each key:
- Acts as a member. A key is bound to the member who owns it and to one workspace. Requests made with it are attributed to that member.
- Is shown once. The full secret appears only at creation time. The platform stores a hash, plus the first 12 characters so you can recognise the key in the list later.
- Takes effect live. Revocation, scope edits, expiry, and member deactivation are checked on every request. There is no propagation delay.
- Tracks usage. The key's last-used timestamp updates as it is used (at one-minute resolution), so unused keys are easy to spot and retire.
Treat keys like passwords
Store keys in environment variables or a secret manager. Never ship a key in client-side JavaScript, a mobile app binary, or a public repository. If a key leaks, revoke or rotate it immediately; the change applies on the next request.
Scopes
A key is either full access or scoped:
- A full access key resolves permissions exactly like the owning member: their roles decide what the key can do.
- A scoped key carries an explicit list of permission keys (for example
tasks.tasks.createorcrm.deals.read). That list is the key's entire permission set. Role grants are not consulted, and there is no admin bypass: a scoped key created by an owner still cannot act outside its scopes.
Scoped keys are the right default for integrations. Grant only the permission keys the integration needs, and widen later if required.
Managing keys via the API
Key management itself is admin-gated and available over the API:
| Method | Route | Purpose |
|---|---|---|
GET |
/api-keys |
List the workspace's keys (prefix, scopes, expiry, last used) |
POST |
/api-keys |
Create a key; the response contains the secret, once |
PUT |
/api-keys/:key_id |
Rename, change scopes, set expiry, or revoke |
PUT |
/api-keys/:key_id/secret |
Rotate: issue a new secret for the same key record |
Session authentication (browser calls)
Inside the product, app frontends call the API with the signed-in user's session. The session cookie is set on .saaslivery.com, so it reaches the API subdomain as long as the request opts in to credentials:
fetch(`${ctx.api_url}/tasks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Version': '2026-03-15',
},
credentials: 'include',
body: JSON.stringify({ title: 'New task' }),
})
credentials: 'include' is essential. Without it the browser will not send the session cookie cross-subdomain and the API responds with 401 Unauthorized.
App templates get MicroAPI from the platform base template, which wraps this pattern (headers, credentials, error parsing) so handlers never repeat it. See HTMX patterns for how it fits into app UIs.
What a failed authentication looks like
| Situation | Response |
|---|---|
| No credential on a protected route | 401 {"error": "Authentication required"} |
| Invalid, revoked, or expired API key | 401 {"error": "Invalid, revoked, or expired API key"} |
| Valid credential, missing permission | 403 with an error message naming the denied action |
A bad API key never falls back to the session cookie. A request that presents an slk_ token is judged on that token alone, so a scoped key cannot quietly borrow a browser session's wider access.