> ## 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.

# Trigger an outbound call

> Starts an outbound call from one of your agents to a number in E.164
format. The call is charged to your wallet when it ends (not when it
is triggered) at your plan's per-minute voice rate, prorated by the
second. The API is available from the Starter plan up.

This endpoint dials with agents on the ElevenLabs runtime. `agent_id`
accepts the Ryvo uuid of the agent (as listed by `GET /v1/agents`) or
its ElevenLabs id (`agent_...`). Engine agents cannot be dialed by API
yet.

If your wallet is empty, your subscription lapsed or the agent is
paused, the request answers `402` or `422` and the call is **not**
placed.

This endpoint has its own per-IP limit of 10 requests per minute. To
avoid duplicates on retries, send an `Idempotency-Key` header.




## OpenAPI

````yaml /openapi.yaml post /v1/calls
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, on fixed one-minute windows:


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

    A `429 rate_limit_exceeded` response carries a `Retry-After` header with

    the seconds to wait (always at least 1).


    ## 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`, `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: Models
    description: The public model catalog with credits and USD per plan. No authentication.
paths:
  /v1/calls:
    post:
      tags:
        - Calls
      summary: Trigger an outbound call
      description: |
        Starts an outbound call from one of your agents to a number in E.164
        format. The call is charged to your wallet when it ends (not when it
        is triggered) at your plan's per-minute voice rate, prorated by the
        second. The API is available from the Starter plan up.

        This endpoint dials with agents on the ElevenLabs runtime. `agent_id`
        accepts the Ryvo uuid of the agent (as listed by `GET /v1/agents`) or
        its ElevenLabs id (`agent_...`). Engine agents cannot be dialed by API
        yet.

        If your wallet is empty, your subscription lapsed or the agent is
        paused, the request answers `402` or `422` and the call is **not**
        placed.

        This endpoint has its own per-IP limit of 10 requests per minute. To
        avoid duplicates on retries, send an `Idempotency-Key` header.
      operationId: createCall
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier (1 to 255 printable ASCII characters) so that
            retries of the same request return the same response without
            placing a second call. Cached for 24 hours. Reusing the key with
            a different body returns `409 idempotency_conflict`.
          schema:
            type: string
            maxLength: 255
            example: lead-abc-2026-04-30-001
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCallRequest'
            examples:
              minimal:
                summary: Minimal request
                value:
                  agent_id: 4d0f3c2a-6b7e-4a1c-9f2d-1e2b3c4d5e6f
                  to: '+5215555550100'
              withMetadata:
                summary: With variables for the agent
                value:
                  agent_id: agent_8801kpabc123
                  to: '+5215555550100'
                  metadata:
                    lead_name: Juan García
                    deal_id: OPP-2026-0042
      responses:
        '201':
          description: The call was placed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedCall'
        '400':
          description: >-
            Invalid JSON, invalid body, or a malformed `Idempotency-Key`
            (`invalid_idempotency_key`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: >-
            Empty wallet or inactive subscription (`payment_required`, with
            `primary_reason` and `reasons`), or this key spent its credit budget
            for the cycle (`key_budget_exhausted`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateError'
        '403':
          description: >-
            Read-only key (`forbidden`), missing `calls:write`
            (`insufficient_scope`), or the agent does not exist or belongs to
            another account (`agent_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            The `Idempotency-Key` was reused with a different body
            (`idempotency_conflict`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: The agent is not active (`agent_disabled`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          description: >-
            The voice provider was unreachable (`upstream_unreachable`) or
            rejected the call (`upstream_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: The service is temporarily unavailable (`service_unavailable`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CreateCallRequest:
      type: object
      required:
        - agent_id
        - to
      properties:
        agent_id:
          type: string
          description: >-
            The Ryvo uuid of the agent, or its ElevenLabs id (`agent_...`). Must
            belong to your account.
          example: 4d0f3c2a-6b7e-4a1c-9f2d-1e2b3c4d5e6f
        to:
          type: string
          pattern: ^\+[1-9]\d{6,14}$
          description: The destination number in E.164 format.
          example: '+5215555550100'
        metadata:
          type: object
          description: |
            Optional variables passed to the agent as dynamic variables.
            At most 5 KB. Keys up to 64 characters; values must be strings
            (up to 2000 characters), numbers, booleans or null.
          additionalProperties:
            oneOf:
              - type: string
                maxLength: 2000
              - type: number
              - type: boolean
              - type: 'null'
          example:
            lead_name: Juan García
            deal_id: OPP-2026-0042
    CreatedCall:
      type: object
      required:
        - id
        - agent_id
        - to
        - status
        - created_at
      properties:
        id:
          type: string
          description: >-
            The call handle (`call_<conversation_id>`). Accepted by `GET
            /v1/calls/{id}`.
          example: call_conv_01abcdef
        agent_id:
          type: string
          description: The `agent_id` you sent.
          example: 4d0f3c2a-6b7e-4a1c-9f2d-1e2b3c4d5e6f
        to:
          type: string
          example: '+5215555550100'
        status:
          type: string
          enum:
            - initiated
          description: >-
            To learn how the call ended, subscribe to `call.completed` and
            `call.failed` webhooks or poll `GET /v1/calls/{id}`.
        created_at:
          type: string
          format: date-time
          example: '2026-04-30T18:32:11.123Z'
    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
    GateError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            primary_reason:
              type: string
              description: The main reason the call was denied. Useful to show specific UX.
              example: wallet
            reasons:
              type: array
              description: Every reason the call was denied.
              items:
                type: string
              example:
                - wallet
                - agent:pausado
  responses:
    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
    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_...`

````