Every request to the platform API carries a bearer token. There are two kinds, and using the wrong one is the most common integration mistake.
API keys vs access tokens
API keys identify a machine. They are long-lived, scoped to a project, and belong in server-side code only. A key in a browser bundle is a compromised key.
Access tokens identify a person. They are short-lived JWTs issued after login, and they carry the user's roles.
Making an authenticated request
curl https://api.cymbiote.com/v1/posts \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json"
Token lifetimes
| Token | Lifetime | Rotates |
|---|---|---|
| API key | Until revoked | Manually |
| Access token | 15 minutes | On refresh |
| Refresh token | 30 days | On every use |
Refresh tokens are single-use. Presenting one returns a new pair and invalidates the old refresh token immediately.
Handling expiry
Do not pre-emptively refresh on a timer — clock skew makes that unreliable. Refresh on a 401, retry the original request once, and give up if the retry also fails.
async function request(path: string, init: RequestInit = {}) {
let response = await fetch(path, withAuth(init));
if (response.status === 401) {
await refreshTokens(); // throws if the refresh itself fails
response = await fetch(path, withAuth(init));
}
return response;
}
Scopes
Keys are scoped at creation and cannot be widened afterwards — create a new key instead. Available scopes are content:read, content:write, media:write, analytics:read and admin.
Grant the narrowest scope that works. A key that only reads content cannot be used to delete anything if it leaks.