Skip to content

Group Call Fanout

Use POST /v1/calls/fanout to dial up to 500 outbound PSTN recipients into one call in a single request. The endpoint creates a single call session (one room) and enqueues one PSTN participant per recipient — every recipient joins the same call.

POST /v1/calls/batch is a compatibility alias for the same behavior; see Compatibility alias below.

You can also use the primary Calls API directly by sending multiple pstn participants to POST /v1/calls.

For independent per-recipient attempts, retries, and reports, use Campaigns

Fanout creates one call with many participants in a single room — useful for group-style sessions. If instead you want each recipient to have their own private call, independent retries, follow-ups, and per-recipient reporting, use Campaigns instead. See Campaigns vs. Group Call Fanout for the full comparison.

Fanout Call

POST/v1/calls/fanout

Request Body

FieldTypeRequiredDefaultDescription
agent_iduuidYesAgent persona to dial with
tostring[]Yes (unless recipients)Recipient phone numbers in E.164 format (e.g., ["+15559990001", "+15559990002"])
recipientsRecipient[]Yes (unless to)Recipient objects with per-recipient overrides
from_numberstringNoTenant defaultShared caller ID in E.164 format. If omitted, Rymi resolves your default number
fanout_idstringNoAuto-generatedCaller-supplied identifier for tracking this fanout. When omitted, Rymi generates one (prefixed fanout_)
batch_idstringNoFalls back to fanout_idCompatibility alias for fanout_id. If both are supplied, fanout_id wins
metadataobjectNo{}Metadata stored on the call record. Fanout stamps source: "group_call_fanout", fanout_id, and batch_id onto it
variablesobjectNo{}Shared variables passed into queued telephony jobs
post_callPost-call objectNoAgent defaultPer-fanout post-call intelligence override

Recipient Object

Use recipients instead of to when you need per-recipient caller ID or metadata.

FieldTypeRequiredDefaultDescription
to or identitystringYesRecipient phone number in E.164 format
from_numberstringNoFanout-level from_numberPer-recipient caller ID override
metadataobjectNo{}Per-recipient metadata

Example: Simple Fanout

bash
curl -X POST https://api.rymi.live/v1/calls/fanout \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "550e8400-e29b-41d4-a716-446655440000",
    "to": ["+15559990001", "+15559990002"],
    "from_number": "+15559990000",
    "metadata": { "fanout_label": "promo-q1" },
    "variables": { "campaign": "spring-offer" }
  }'
ts
const result = await rymi.calls.fanout({
  agent_id: "550e8400-e29b-41d4-a716-446655440000",
  to: ["+15559990001", "+15559990002"],
  from_number: "+15559990000",
  metadata: { fanout_label: "promo-q1" },
  variables: { campaign: "spring-offer" },
});

console.log(result.fanout_id, result.queued);
python
result = rymi.calls.fanout(
    "550e8400-e29b-41d4-a716-446655440000",
    to=["+15559990001", "+15559990002"],
    from_number="+15559990000",
    metadata={"fanout_label": "promo-q1"},
    variables={"campaign": "spring-offer"},
)

Example: Per-Recipient Metadata

bash
curl -X POST https://api.rymi.live/v1/calls/fanout \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipients": [
      {
        "to": "+15559990001",
        "from_number": "+15559990000",
        "metadata": { "name": "Alice", "plan": "Gold" }
      },
      {
        "to": "+15559990002",
        "metadata": { "name": "Bob", "plan": "Silver" }
      }
    ],
    "from_number": "+15559990000",
    "fanout_id": "promo-q1-2026",
    "variables": { "campaign": "spring-offer" }
  }'
ts
const result = await rymi.calls.fanout({
  agent_id: "550e8400-e29b-41d4-a716-446655440000",
  recipients: [
    {
      to: "+15559990001",
      from_number: "+15559990000",
      metadata: { name: "Alice", plan: "Gold" },
    },
    {
      to: "+15559990002",
      metadata: { name: "Bob", plan: "Silver" },
    },
  ],
  from_number: "+15559990000",
  fanout_id: "promo-q1-2026",
  variables: { campaign: "spring-offer" },
});
python
result = rymi.calls.fanout(
    "550e8400-e29b-41d4-a716-446655440000",
    to=[],
    recipients=[
        {
            "to": "+15559990001",
            "from_number": "+15559990000",
            "metadata": {"name": "Alice", "plan": "Gold"},
        },
        {
            "to": "+15559990002",
            "metadata": {"name": "Bob", "plan": "Silver"},
        },
    ],
    from_number="+15559990000",
    fanout_id="promo-q1-2026",
    variables={"campaign": "spring-offer"},
)

Response 202

json
{
  "id": "call_abc123",
  "room_name": "call_call_abc123",
  "status": "queued",
  "fanout_id": "promo-q1-2026",
  "batch_id": "promo-q1-2026",
  "queued": 2,
  "participants": [
    {
      "id": "participant_1",
      "transport": "pstn",
      "identity": "+15559990001",
      "status": "queued",
      "telephony_leg_id": "leg_1",
      "job_id": "job_1"
    },
    {
      "id": "participant_2",
      "transport": "pstn",
      "identity": "+15559990002",
      "status": "queued",
      "telephony_leg_id": "leg_2",
      "job_id": "job_2"
    }
  ]
}
FieldTypeDescription
fanout_idstringThe resolved fanout identifier (supplied or auto-generated). Also written to metadata.fanout_id on the call record
batch_idstringCompatibility alias — always equal to fanout_id
queuedintegerNumber of PSTN participants queued (recipient count)

Errors

StatusMeaning
400Validation error (missing agent_id, empty to/recipients, exceeds 500 limit, invalid E.164 numbers)
400One or more recipients are on the DNC list (rejected before queueing)
401Missing or invalid API key
402Insufficient credits
403Publishable keys cannot use the fanout API
404Agent not found for this tenant
409Agent is not publish-ready (code: agent_not_publish_ready): resolve publish validation blockers before starting live calls

MCP tool

Agents connected through the Rymi MCP server can trigger a fanout with the fanout_call tool, which maps directly to this endpoint. (The batch_call tool remains available as the compatibility alias.)


Compatibility alias: /calls/batch

POST /v1/calls/batch is a compatibility alias for POST /v1/calls/fanout — same room-centric behavior, same request body (it accepts batch_id in place of fanout_id), and the same 202 response (returning batch_id). New integrations should prefer /v1/calls/fanout; existing /v1/calls/batch callers, rymi.calls.batch() (Node + Python), and the batch_call MCP tool continue to work unchanged.

POST/v1/calls/batch
bash
curl -X POST https://api.rymi.live/v1/calls/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "550e8400-e29b-41d4-a716-446655440000",
    "to": ["+15559990001", "+15559990002"],
    "from_number": "+15559990000",
    "batch_id": "promo-q1-2026"
  }'
ts
const result = await rymi.calls.batch({
  agent_id: "550e8400-e29b-41d4-a716-446655440000",
  to: ["+15559990001", "+15559990002"],
  from_number: "+15559990000",
  batch_id: "promo-q1-2026",
});
python
result = rymi.calls.batch(
    "550e8400-e29b-41d4-a716-446655440000",
    to=["+15559990001", "+15559990002"],
    from_number="+15559990000",
    batch_id="promo-q1-2026",
)

Direct Participant Fanout

If you need mixed WebRTC/PSTN rooms or participant-specific metadata, send multiple participants directly to POST /v1/calls:

json
{
  "agent_id": "550e8400-e29b-41d4-a716-446655440000",
  "participants": [
    {
      "transport": "pstn",
      "identity": "+15559990001",
      "from_number": "+15559990000",
      "metadata": { "name": "Alice", "plan": "Gold" }
    },
    {
      "transport": "pstn",
      "identity": "+15559990002",
      "from_number": "+15559990000",
      "metadata": { "name": "Bob", "plan": "Silver" }
    }
  ],
  "variables": { "campaign": "spring-offer" }
}

Queue Stats

Check the health and current status of your queued fanout calling worker.

GET/v1/calls/queue/stats

Response 200

json
{
  "waiting": 150,
  "active": 10,
  "completed": 45,
  "failed": 2
}
FieldTypeDescription
waitingintegerJobs waiting to be processed
activeintegerJobs currently being processed
completedintegerSuccessfully completed jobs
failedintegerJobs that failed

Queue Status Flow

Each queued PSTN participant progresses independently:

text
queued → dialing → ringing → in_progress → completed
                                          → failed

DNC Filtering

Contacts on your DNC registry are rejected during request validation and are never queued. See Compliance.