Authentication
Base URL, API keys, scopes, auth headers, auth errors and rate limits.
Read as MarkdownEverything you need to make an authenticated call to the REST API.
Base URL https://app.riffads.com/api/v1
Auth Authorization: Bearer sk_live_...- One version:
v1. No version header. riffads.comis the marketing site. It does not serve/api/v1.- MCP clients use OAuth, not keys. See MCP connect.
Server side only
No CORS headers, no OPTIONS handler. The key is a secret. Call from a backend, task runner, CI job or terminal. Never a browser.
Send the key
export RIFFADS_API_KEY="sk_live_..."
curl -s https://app.riffads.com/api/v1/capabilities \
-H "Authorization: Bearer $RIFFADS_API_KEY"Header rules
x-api-key: sk_live_... works anywhere Authorization does.
| Case | Result |
|---|---|
| Both headers sent | A usable Authorization: Bearer wins. A non-Bearer Authorization counts as absent, so x-api-key is read |
| Scheme casing | Case-insensitive: bearer, Bearer, BEARER |
| Extra whitespace | Allowed between scheme and key |
Non-Bearer scheme (Basic abc), no x-api-key | Counts as no header. Plain 401 |
| Key in a query param | Not supported. 401 |
Missing header, malformed header and unknown key all get the same 401, byte for byte.
Get a key
Owners and admins create keys at app.riffads.com/api-keys. No API endpoint creates, rotates or lists keys.
- The workspace plan must include the API. Every paid plan does.
- Prefix is
sk_live_everywhere. Nosk_test_, no sandbox. Every call is real. - Shown once. Stored as a hash. Lost it? Create a new one.
- A key belongs to the workspace, not the person who made it. It only reaches its own workspace.
- Keys never expire. They work until revoked.
- Name is required, up to 60 characters. Name it after where it lives (
CI pipeline, staging). - A key can carry an optional 24 hour spend cap, set in the app.
Scopes
Pick scopes when you create the key. They can never be widened. Need another scope? Create a new key.
| Scope | Opens |
|---|---|
| Read | On every key. Capabilities, actors, voices, balance, estimates, all generation, batch and workflow reads. Spends nothing |
| Generate | Read, plus uploads and POST /generations |
| Workflows | Read, plus both workflow invoke routes |
A Generate key can't invoke a workflow. A Workflows key can't submit a generation. Need both? Tick both.
| Endpoint | Scope |
|---|---|
GET /capabilities, GET /capabilities/{id} | Read |
POST /estimates | Read |
GET /actors, GET /voices | Read |
GET /generations/{id}, GET /generations/{id}/wait | Read |
GET /batches/{id} | Read |
GET /workflows/templates, GET /workflow-runs/{id} | Read |
POST /generations | Generate |
POST /uploads, POST /uploads/{assetId}/finalize | Generate |
POST /workflows/{id}/invoke | Workflows |
POST /workflows/templates/{key}/invoke | Workflows |
A read-only key can't spend. Safe for dashboards and reports.
Revoke and rotate
- Revoke in the app. It takes effect on the next call.
- A revoked key answers
401 not_authorized. - Work already running finishes.
- To rotate: create the new key, deploy it (both work meanwhile), then revoke the old one.
- No cap on keys per workspace. One key per integration keeps limits and revocation separate.
- Committed a key to a repo? Revoke it now.
Auth errors
Every failure is wrapped in error. Every 401 sends WWW-Authenticate: Bearer realm="RiffAds".
{
"error": {
"ok": false,
"code": "not_authorized",
"message": "This RiffAds API key is not valid. Check the Authorization header, or create a new key at riffads.com.",
"retryable": false,
"credits_charged": 0
}
}| Cause | code | HTTP | Retry |
|---|---|---|---|
| No header, non-Bearer scheme, blank or unknown key | not_authorized | 401 | no |
| Key revoked | not_authorized | 401 | no |
| Key expired (only very old keys) | not_authorized | 401 | no |
| Key's request allowance used up | quota_exceeded | 429 | no |
| Too many requests | rate_limited | 429 | yes |
| Key lacks the scope this endpoint needs | insufficient_scope | 403 | no |
| Workspace deleted | workspace_unavailable | 410 | no |
| Plan does not include the API | required_plan | 403 | no |
| Read-only connection tried to spend | read_only_connection | 403 | no |
| Key check failed on our side | internal_error | 500 | yes |
When several things are wrong, you get them in this order: header, key (revoked, allowance, rate limit, scope), workspace, plan.
Messages say riffads.com
Error messages say "create a new key at riffads.com". Keys are made at app.riffads.com/api-keys.
Rate limits
Two brakes. Either can return 429 rate_limited.
Per key: 120 requests a minute. The number is copied onto the key when it is created.
Per action: fixed one minute windows, counted per key. Two keys never share a bucket.
| Bucket | Per minute | Endpoints |
|---|---|---|
| Reads | 120 | Capabilities, actors, voices, balance, generation and batch reads, wait, templates, workflow runs |
| Estimates | 60 | POST /estimates |
| Submits | 20 | POST /generations |
| Uploads | 30 | POST /uploads and finalize |
| Workflow runs | 20 | Both invoke routes |
- One
waitcall costs 1 read, however long it blocks. - No
X-RateLimit-*headers. - A 429 sends
Retry-Afteronly when the body hasretry_after_seconds. Same number. quota_exceededis a 429 you can't retry and sends noRetry-After.
All numbers: limits. Retry rules: conventions.