Authentication & scopes
How you authenticate, what a key is allowed to do, and the cross-cutting conventions — errors, idempotency, rate limits, and pagination — that every endpoint follows.
Credentials
Send a bearer credential on every request: a vbk_ API key (Argon2id-verified, created in the dashboard) or a session token from login. Both go in Authorization: Bearer …; the x-api-key header is also accepted. API keys show their secret only once at creation — only a hash and a 16-char prefix are stored.
# either header works; a bearer key or a dashboard session token
curl https://api.ollanode.com/v1/whoami -H "Authorization: Bearer vbk_..."
curl https://api.ollanode.com/v1/whoami -H "x-api-key: vbk_..." API keys
Key management requires the coarse admin scope and is human-only — agent keys can never manage keys.
/v1/api-keys admin Create a key. Role presets: owner → [admin]; agent → [read,write] (actor_type=agent, and forbidden from holding admin); viewer → [read]; default → [read,write]. Add least-privilege grants with extra_scopes, and constrain with expires_in_secs, allowed_ip, and daily_request_limit. The secret is shown once.
curl -X POST https://api.ollanode.com/v1/api-keys \
-H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"ci-agent","role":"agent",
"extra_scopes":["videos:read","zones:purge"],
"expires_in_secs":86400,"allowed_ip":"203.0.113.7",
"daily_request_limit":5000}' { "id":"key_...", "name":"ci-agent", "prefix":"vbk_1234abcd...",
"scopes":["read","write"], "extra_scopes":["videos:read","zones:purge"],
"actor_type":"agent", "expires_at":"...", "daily_request_limit":5000,
"key":"vbk_<48 hex — shown once>" } - Mint a short-lived, IP-pinned, quota-capped key for an AI agent or CI job.
- Issue a read-only key (
role:"viewer") for a reporting dashboard.
/v1/api-keys admin List keys for the project. Secrets are never returned — only the id, name, prefix, scopes, and lifecycle fields.
curl https://api.ollanode.com/v1/api-keys -H "Authorization: Bearer $SESSION" /v1/api-keys/{id} admin Revoke a key. Revocation is immediate — the per-request prefix lookup filters revoked keys on the very next call.
curl -X DELETE https://api.ollanode.com/v1/api-keys/key_abc -H "Authorization: Bearer $SESSION" - Rotate a key by creating a new one, then revoking the old — there is no dedicated rotate endpoint for API keys.
Roles
Human users hold one org role, which derives their coarse scopes. Owner-only powers (delete org, transfer ownership) key off the project's owner marker, so owner and admin share scopes.
| Role | Coarse scopes | Notes |
|---|---|---|
owner | read, write, admin | Plus org delete & ownership transfer |
admin | read, write, admin | Team, keys, approvals decisions |
member | read, write | Product work, no admin |
viewer | read | Read-only |
Scopes: coarse and fine-grained
A call is authorized if the key has the coarse admin scope, or the exact fine-grained resource:action grant (or a resource:* wildcard), or the matching coarse preset — read satisfies any read/list/get; write satisfies every other action. So document the fine-grained scope as the least-privilege grant; a broad write/read key also passes.
| Resource | Fine-grained scopes |
|---|---|
| Videos & libraries | videos:read · videos:write · videos:delete · libraries:write · libraries:delete |
| CDN zones | zones:write · zones:purge · zones:rotate · zones:delete |
| Storage | storage:read · storage:write · storage:delete |
| DNS | dns:read · dns:write · dns:delete |
| Edge functions | functions:read · functions:write · functions:delete |
| KV store | kv:read · kv:write |
| Webhooks / analytics / logs | webhooks:read · webhooks:write · analytics:read · logs:read |
| Governance | approvals:create · approvals:read |
team:write, keys:write, org:delete — appear in the capability map but can never be granted to an agent key. See AI agents & governance.Errors
Every error is a stable JSON body — { "error": { "code", "message" } } — with a conventional status. Internal errors never leak detail.
| Status | Meaning |
|---|---|
400 | Validation — malformed or invalid input |
401 | Unauthorized — missing/invalid/expired credential |
403 | Forbidden — authenticated but not allowed (or human-only) |
404 | Not found (also cross-tenant isolation) |
409 | Conflict — e.g. owner can't self-delete before handling the org |
429 | Rate limited — see Retry-After |
202 | Approval pending — an agent action is queued for a human |
Idempotency
Send an Idempotency-Key header on any POST/PATCH/PUT/DELETE. It's DB-backed and keyed by caller + method + path + query + body digest: a retry replays the original 2xx (response carries idempotency-replayed: true), a concurrent duplicate gets 409, and the same key with a different body gets 422.
curl -X POST https://api.ollanode.com/v1/videos \
-H "Authorization: Bearer $OLLANODE_API_KEY" \
-H "Idempotency-Key: 3f9c-launch-video" \
-H "Content-Type: application/json" \
-d '{"title":"Launch"}' Rate limits
Requests are token-bucketed per key (or per IP for unauthenticated calls). Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also sets Retry-After and X-RateLimit-Reset (seconds). Per-key daily caps surface as 429 too.
Request IDs
Pass X-Request-Id to correlate a call across logs and audit, or read the one the API mints back on every response (including errors). Audit entries store it.
Pagination
Collection GETs take ?limit= (default 25, max 100) and ?offset=, and return { items, total, limit, offset }. Storage file listing is the one cursor-based exception (?cursor= → next_cursor).
curl "https://api.ollanode.com/v1/videos?limit=25&offset=50" \
-H "Authorization: Bearer $OLLANODE_API_KEY"