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
Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
agent_id | uuid | Yes | — | Agent persona to dial with |
to | string[] | Yes (unless recipients) | — | Recipient phone numbers in E.164 format (e.g., ["+15559990001", "+15559990002"]) |
recipients | Recipient[] | Yes (unless to) | — | Recipient objects with per-recipient overrides |
from_number | string | No | Tenant default | Shared caller ID in E.164 format. If omitted, Rymi resolves your default number |
fanout_id | string | No | Auto-generated | Caller-supplied identifier for tracking this fanout. When omitted, Rymi generates one (prefixed fanout_) |
batch_id | string | No | Falls back to fanout_id | Compatibility alias for fanout_id. If both are supplied, fanout_id wins |
metadata | object | No | {} | Metadata stored on the call record. Fanout stamps source: "group_call_fanout", fanout_id, and batch_id onto it |
variables | object | No | {} | Shared variables passed into queued telephony jobs |
post_call | Post-call object | No | Agent default | Per-fanout post-call intelligence override |
Recipient Object
Use recipients instead of to when you need per-recipient caller ID or metadata.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
to or identity | string | Yes | — | Recipient phone number in E.164 format |
from_number | string | No | Fanout-level from_number | Per-recipient caller ID override |
metadata | object | No | {} | Per-recipient metadata |
Example: Simple Fanout
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" }
}'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);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
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" }
}'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" },
});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
{
"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"
}
]
}| Field | Type | Description |
|---|---|---|
fanout_id | string | The resolved fanout identifier (supplied or auto-generated). Also written to metadata.fanout_id on the call record |
batch_id | string | Compatibility alias — always equal to fanout_id |
queued | integer | Number of PSTN participants queued (recipient count) |
Errors
| Status | Meaning |
|---|---|
400 | Validation error (missing agent_id, empty to/recipients, exceeds 500 limit, invalid E.164 numbers) |
400 | One or more recipients are on the DNC list (rejected before queueing) |
401 | Missing or invalid API key |
402 | Insufficient credits |
403 | Publishable keys cannot use the fanout API |
404 | Agent not found for this tenant |
409 | Agent 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.
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"
}'const result = await rymi.calls.batch({
agent_id: "550e8400-e29b-41d4-a716-446655440000",
to: ["+15559990001", "+15559990002"],
from_number: "+15559990000",
batch_id: "promo-q1-2026",
});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:
{
"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.
Response 200
{
"waiting": 150,
"active": 10,
"completed": 45,
"failed": 2
}| Field | Type | Description |
|---|---|---|
waiting | integer | Jobs waiting to be processed |
active | integer | Jobs currently being processed |
completed | integer | Successfully completed jobs |
failed | integer | Jobs that failed |
Queue Status Flow
Each queued PSTN participant progresses independently:
queued → dialing → ringing → in_progress → completed
→ failedDNC Filtering
Contacts on your DNC registry are rejected during request validation and are never queued. See Compliance.

