REST API

Actors and voices

List the actors and voices you can use, get their ids, pass them into a submit.

Read as Markdown

Find who speaks and in what voice. You get ids and facets, then pass the ids into a submit.

EndpointReturnsPages
GET /actorsActors this workspace may useyes
GET /voicesVoices this workspace may useyes
  • Scope read. Every live key has it.
  • Rate bucket agent_read: 120 calls a minute per key, shared with other reads.

Brands and products have no API

Brands, products, new actors and new voices are made in the app at app.riffads.com. There is no REST, MCP or CLI route to create or read them.

Actors

GET/api/v1/actorsAPI key

The RiffAds cast plus the active actors your workspace made. Actors still processing, or disabled, are hidden.

ParameterTypeDefaultNotes
searchstringnoneName or description, case insensitive. % and _ are literal
limitinteger25Clamped to 1 to 50. A page holds at most 48, so 49 and 50 return 48. limit=1000 gets a page, not an error
cursorstringnoneThe previous next_cursor, sent back exactly

No other params. No gender, tag or age filter: match gender, age_band and tags yourself.

Terminal
curl -sG https://app.riffads.com/api/v1/actors \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  --data-urlencode "search=warm" \
  --data-urlencode "limit=50"
Response: 200
{
  "ok": true,
  "actors": [
    {
      "actor_id": "act_0193c8f0a1b24e7f9d3c5a6b7e8f0011",
      "name": "Maya",
      "description": "Warm, direct, speaks to camera at home",
      "gender": "female",
      "age_band": "adult",
      "tags": ["kitchen", "morning", "standing", "casual"],
      "default_voice_id": "voc_0193c8f0a1b24e7f9d3c5a6b7e8f0022",
      "default_voice_name": "Maya (natural)",
      "is_platform": true
    }
  ],
  "next_cursor": "eyJpc09yZyI6ZmFsc2UsImlzQ29yZSI6dHJ1ZSwiY3JlYXRlZEF0IjoiMjAyNi0wOS0wMVQxMDoxMjowMFoiLCJpZCI6ImFjdF8wMTkzIn0"
}

Prop

Type

  • Order: your actors first, then the core cast, then the rest. Newest first in each group.
  • No total. The body has only ok, actors and next_cursor. Page until next_cursor is null.
  • Plan gated. Higher plans see more of the cast. Your own actors are always listed.

No faces over the API

No preview image or clip comes back, ever. You get the id and the facets. To see faces, open app.riffads.com.

Voices

GET/api/v1/voicesAPI key

The RiffAds voice library plus voices your workspace cloned or designed. Active only, sorted by name.

ParameterTypeDefaultNotes
searchstringnoneName only, case insensitive. Tags are not searched
languagestringnoneExact tag, like en or es. Not a prefix
limitinteger25Clamped to 1 to 50
cursorstringnoneThe previous next_cursor, sent back exactly
Terminal
curl -sG https://app.riffads.com/api/v1/voices \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  --data-urlencode "language=en"
Response: 200
{
  "ok": true,
  "voices": [
    {
      "voice_id": "voc_0193c8f0a1b24e7f9d3c5a6b7e8f0022",
      "name": "Maya (natural)",
      "language": "en",
      "gender": "female",
      "tags": ["young_adult", "american", "conversational"],
      "is_cloned": false,
      "is_premium": false,
      "is_platform": true
    }
  ],
  "total": 61,
  "next_cursor": "25"
}

Prop

Type

  • total counts matches before paging. GET /actors has no total.
  • No preview audio comes back.
  • Cloned voices have no language and no gender. A language filter misses them. Find them by name.

The two cursors differ. Treat both as opaque.

The actor cursor is a base64url keyset. The voice cursor is an offset as a string. Send next_cursor back exactly, never build one. A bad cursor restarts from the top, not an empty page. Conventions.

From ids to a submit

actor_id and voice_id go at the top level of the submit body, not in config:

Request
{
  "capability_id": "actor_ultra",
  "actor_id": "act_0193c8f0a1b24e7f9d3c5a6b7e8f0011",
  "voice_id": "voc_0193c8f0a1b24e7f9d3c5a6b7e8f0022",
  "approved_voice_generation_id": "gen_0193c8f0a1b24e7f9d3c5a6b7e8f0044",
  "config": { "script": "Two weeks in, and I am not going back." },
  "max_credits": 660
}
  1. A talking actor is 2 generations. First tts, then actor_ultra with the tts generation_id as approved_voice_generation_id. Script, actor and voice must match the tts job. Talking actor guide.
  2. voice_id is optional when the actor has a default_voice_id.
  3. Body voice_id and config voiceId are different layers. Read config_schema from GET /capabilities/{capability_id}.
  4. Another workspace's id answers 404 not_found, same as a missing id. Never forbidden.

Errors

Standard error envelope. See conventions.

CodeHTTPRetryableCause
not_authorized401noNo key, revoked, or unknown
rate_limited429yesOver 120 agent_read calls a minute. Carries retry_after_seconds and Retry-After
quota_exceeded429noKey used its request allowance. Waiting a bit won't help
required_plan403noPlan doesn't include the agent API
workspace_unavailable410noWorkspace deleted
internal_error500yesOur fault. Try again

A balance internal_error says so, so it never reads as an empty wallet:

Response: 500
{
  "error": {
    "ok": false,
    "code": "internal_error",
    "message": "We could not read this workspace's credit balance. This is not a balance problem: try again.",
    "retryable": true,
    "credits_charged": 0
  }
}

MCP: list_actors and list_voices take the same params and return the same shape. MCP tools.

On this page