> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ryvo.so/llms.txt
> Use this file to discover all available pages before exploring further.

# List voices

> The voices you can give an Engine agent. The catalog is synced daily
from ElevenLabs (your workspace and the shared library, Spanish and
English), Cartesia and Deepgram Aura-2; your account's private voices
are included too. Recommended voices come first, then by provider and
name. Voices that carry an extra provider fee are not listed.

Send the `voice_id` of the voice you pick as `voice.voice_id`, with its
`provider` as `voice.provider`, when you create or update an agent.




## OpenAPI

````yaml /openapi.yaml get /v1/voices
openapi: 3.1.0
info:
  title: Ryvo API
  version: 1.0.0
  description: >
    The Ryvo public API lets you drive your AI voice and chat agents from your

    own systems: trigger calls, read conversations and transcripts, manage

    agents, knowledge base documents, phone numbers, outbound campaigns and

    webhooks, and read billing, team and log data. It is the same platform the

    portal uses, exposed over plain HTTPS and JSON.


    **Base URL**: `https://api.ryvo.so`


    ## Authentication


    Every request (except `GET /v1/models`) carries an API key as a Bearer

    token:


    ```

    Authorization: Bearer ryvo_live_...

    ```


    Keys are created in the portal under **Settings > API keys**

    (`https://app.ryvo.so/developers`). The full token is shown once at

    creation time. The API is available from the Starter plan up.


    ## Scopes


    Each key carries a list of scopes in the form `resource:action`, chosen

    when the key is created. An endpoint requires exactly one scope (listed

    on each operation as `x-scope`). A key without it receives

    `403 insufficient_scope`, and the message names the missing scope.


    | Scope | Opens |

    |---|---|

    | `agents:read`, `agents:write` | `/v1/agents` |

    | `calls:read`, `calls:write` | `/v1/calls` |

    | `conversations:read` | `/v1/conversations` |

    | `knowledge:read`, `knowledge:write` | `/v1/knowledge` |

    | `phone_numbers:read`, `phone_numbers:write` | `/v1/phone-numbers` |

    | `campaigns:read`, `campaigns:write` | `/v1/batch-calls` |

    | `webhooks:read`, `webhooks:write` | `/v1/webhooks` |

    | `integrations:read` | `/v1/integrations` |

    | `team:read` | `/v1/team` |

    | `billing:read` | `/v1/billing` |

    | `logs:read` | `/v1/logs` |


    `GET /v1/me` only needs a valid key and reports the scopes it carries.

    Integrations, team and billing are read-only by design: connecting an

    app is a browser OAuth flow, inviting people is persistent access, and

    buying credits from a loop is the failure mode nobody wants.


    ## Pagination


    Every list takes `?limit=` (1 to 100, default 20) and `?cursor=`, and

    answers `{ "data": [...], "has_more": true|false, "next_cursor": "..."|null
    }`.

    The cursor is opaque: pass `next_cursor` back as `?cursor=` to fetch the

    following page, and stop when `has_more` is `false`.


    ## Idempotency


    `POST /v1/calls` accepts an `Idempotency-Key` header (1 to 255 printable

    ASCII characters). Retrying the same key with the same body returns the

    original response for 24 hours without dialing again; the same key with a

    different body returns `409 idempotency_conflict`.


    ## Rate limits


    Two limits apply, in this order:


    1. **Per IP address**: 300 requests/min across every `/v1` endpoint, as a
       token bucket. `POST /v1/calls` has its own fixed window of 10
       requests/min per IP because it dials a real phone.
    2. **Per account, by plan**: a token bucket shared by every key of the
       account (Starter 10, Pro 40, Scale and Enterprise 120 requests/min).

    A token bucket holds as many tokens as the limit and adds one back every

    `60 / limit` seconds: an idle integration can burst the whole bucket,

    the sustained rate never exceeds the published number, and running out

    costs seconds, not the rest of the minute.


    Every response after authentication carries `X-RateLimit-Limit`,

    `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds when the

    next token is added). A `429 rate_limit_exceeded` response also carries

    `Retry-After` with the seconds to wait (always at least 1).


    If we cannot check your limit (our rate limiting store is down or slow),

    the request is refused with `503 rate_limit_unavailable` and

    `Retry-After: 5`. That is not your quota running out: retry after

    `Retry-After`. This response carries no `X-RateLimit-*` headers.


    ## Errors and request ids


    Every error uses the same envelope: `{ "error", "message", "request_id" }`.

    Branch on `error`, which is a stable code; `message` is for humans and

    may change. The stable codes are `unauthorized`, `forbidden`,

    `insufficient_scope`, `rate_limit_exceeded`, `rate_limit_unavailable`,

    `invalid_json`,

    `invalid_body`, `invalid_query`, `not_found`, `conflict`,

    `unsupported_runtime`, `plan_limit`, `payment_required`,

    `upstream_error` and `internal_error`. `POST /v1/calls` keeps a few older

    codes of its own (`agent_not_found`, `agent_disabled`,

    `idempotency_conflict`, `invalid_idempotency_key`, `key_budget_exhausted`,

    `upstream_unreachable`, `service_unavailable`).


    Every response, success or error, carries an `X-Request-Id` header. Quote

    it when you report a problem.


    A `404 not_found` is returned both when a resource does not exist and

    when it belongs to another account, with the same message: the API does

    not confirm which ids exist.
  contact:
    name: Ryvo support
    email: team@ryvo.so
    url: https://ryvo.so
servers:
  - url: https://api.ryvo.so
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Account
    description: Who the key belongs to and what it can do.
  - name: Agents
    description: >-
      Your agents on both runtimes. Engine agents are fully manageable
      (configuration, versions, publish). ElevenLabs agents are listed and can
      be renamed; their configuration lives in the portal.
  - name: Calls
    description: >-
      Voice conversations (phone and web voice) on both runtimes, and the
      endpoint to trigger an outbound call.
  - name: Conversations
    description: >-
      Every conversation on every channel (voice, chat, WhatsApp), with the same
      shape as calls plus the message count.
  - name: Knowledge base
    description: >-
      Documents and folders that Engine agents consult during a conversation.
      Requires the Engine module on your plan.
  - name: Phone numbers
    description: Numbers imported from your Twilio account and their agent assignment.
  - name: Batch calls
    description: >-
      Outbound campaigns on the ElevenLabs runtime. Uses the `campaigns:*`
      scopes.
  - name: Webhooks
    description: Outbound endpoints, their deliveries and the event catalog.
  - name: Integrations
    description: >-
      The integration catalog with the connection status of your account.
      Read-only.
  - name: Team
    description: Members, invitations and seats. Read-only.
  - name: Billing
    description: Plan, credits, wallet ledger, invoices and daily usage. Read-only.
  - name: Logs
    description: Requests made to this API and the account activity log.
  - name: Voices
    description: >-
      The voice library for Engine agents: ElevenLabs, Cartesia and Deepgram
      voices, synced daily, plus the private voices of your account.
  - name: Models
    description: The public model catalog with credits and USD per plan. No authentication.
paths:
  /v1/voices:
    get:
      tags:
        - Voices
      summary: List voices
      description: |
        The voices you can give an Engine agent. The catalog is synced daily
        from ElevenLabs (your workspace and the shared library, Spanish and
        English), Cartesia and Deepgram Aura-2; your account's private voices
        are included too. Recommended voices come first, then by provider and
        name. Voices that carry an extra provider fee are not listed.

        Send the `voice_id` of the voice you pick as `voice.voice_id`, with its
        `provider` as `voice.provider`, when you create or update an agent.
      operationId: listVoices
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
        - name: provider
          in: query
          description: Only voices from this provider.
          schema:
            type: string
            enum:
              - elevenlabs
              - cartesia
              - deepgram
        - name: language
          in: query
          description: >-
            A two-letter language code the voice speaks, for example `es` or
            `en`.
          schema:
            type: string
            pattern: ^[a-zA-Z]{2,3}$
            example: es
        - name: gender
          in: query
          schema:
            type: string
            enum:
              - female
              - male
              - neutral
        - name: q
          in: query
          description: Case-insensitive search in the voice name.
          schema:
            type: string
            maxLength: 100
        - name: recommended
          in: query
          description: '`true` for the hand-picked voices only, `false` to exclude them.'
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
      responses:
        '200':
          description: A page of voices.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedResponse'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Voice'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    limit:
      name: limit
      in: query
      description: Page size, 1 to 100. Default 20.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    cursor:
      name: cursor
      in: query
      description: The `next_cursor` of the previous page. Opaque.
      schema:
        type: string
        example: eyJvIjoyMH0
  schemas:
    PaginatedResponse:
      type: object
      description: The envelope of every list. `data` is typed per endpoint.
      required:
        - data
        - has_more
        - next_cursor
      properties:
        data:
          type: array
          items: {}
        has_more:
          type: boolean
          description: Whether another page exists.
          example: true
        next_cursor:
          type:
            - string
            - 'null'
          description: Pass as `?cursor=` to fetch the next page. `null` on the last page.
          example: eyJvIjoyMH0
    Voice:
      type: object
      required:
        - provider
        - voice_id
        - name
        - gender
        - age
        - accent
        - language
        - languages
        - locales
        - description
        - use_case
        - preview_url
        - model_compat
        - source
        - recommended
      properties:
        provider:
          type: string
          enum:
            - elevenlabs
            - cartesia
            - deepgram
          example: elevenlabs
        voice_id:
          type: string
          description: The provider's voice id. Send it as `voice.voice_id` on an agent.
          example: 9Godp7dNohUvXk6qp0gS
        name:
          type: string
          example: Regina
        gender:
          type:
            - string
            - 'null'
          enum:
            - female
            - male
            - neutral
            - null
          example: female
        age:
          type:
            - string
            - 'null'
          example: middle_aged
        accent:
          type:
            - string
            - 'null'
          description: The accent as the provider declares it.
          example: mexican
        language:
          type:
            - string
            - 'null'
          description: The main language, as a two-letter code.
          example: es
        languages:
          type: array
          items:
            type: string
          example:
            - es
        locales:
          type: array
          items:
            type: string
          example:
            - es-MX
        description:
          type:
            - string
            - 'null'
        use_case:
          type:
            - string
            - 'null'
          example: conversational
        preview_url:
          type:
            - string
            - 'null'
          description: |
            A sample you can play without credentials, or `null` when the
            provider does not publish one (Cartesia).
        model_compat:
          type: array
          description: The provider models the voice works with. Empty means all of them.
          items:
            type: string
          example: []
        source:
          type: string
          enum:
            - workspace
            - shared
            - public
            - static
            - custom
          description: |
            Where the voice comes from. `shared` voices belong to the
            ElevenLabs shared library; the rest are ready to use.
        recommended:
          type: boolean
          description: One of the voices Ryvo hand-picked for Mexican Spanish.
    Error:
      type: object
      required:
        - error
        - message
        - request_id
      properties:
        error:
          type: string
          description: A stable error code. Branch on this, not on `message`.
          example: unauthorized
        message:
          type: string
          description: A human-readable description. May change between versions.
          example: Invalid or revoked API key.
        request_id:
          type: string
          format: uuid
          description: The request id. Quote it when you report a problem.
          example: f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b
  responses:
    ValidationError:
      description: >-
        The body is not JSON (`invalid_json`), a body field is invalid
        (`invalid_body`), or a query parameter is invalid (`invalid_query`). The
        message names the first offending field.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: invalid_query
            message: limit must be an integer between 1 and 100.
            request_id: f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b
    Unauthorized:
      description: >-
        Missing or malformed `Authorization` header, or an invalid, expired or
        revoked key (`unauthorized`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: unauthorized
            message: Invalid or revoked API key.
            request_id: f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b
    Forbidden:
      description: >-
        The key does not carry the scope this endpoint requires
        (`insufficient_scope`), or it is read-only (`forbidden`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: insufficient_scope
            message: This API key does not have the agents:write scope.
            request_id: f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b
    RateLimited:
      description: >-
        Too many requests for this IP or this account (`rate_limit_exceeded`).
        Wait `Retry-After` seconds.
      headers:
        Retry-After:
          description: Seconds to wait before retrying. Always at least 1.
          schema:
            type: integer
            example: 32
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: rate_limit_exceeded
            message: Rate limit exceeded. Retry in 32s (see the Retry-After header).
            request_id: f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key (ryvo_live_...)
      description: |
        A Bearer token. Create keys in the portal under Settings > API keys
        (`https://app.ryvo.so/developers`). The token has the form
        `ryvo_live_<64 hex characters>` and is shown once.

        **Header:** `Authorization: Bearer ryvo_live_...`

````