{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/voice-2.0/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"product":"Voice v2 (preview)","type":"markdown"},"seo":{"title":"Call Pacing (Batch Calls)","siteUrl":"https://developers.sinch.com","llmstxt":{"title":"Sinch Developer Documentation","description":"LLMs.txt containing a map of all the documentation files for Sinch.","sections":[{"title":"Numbers API","description":"The Numbers API enables you to search for, view, and activate numbers. It's considered a precursor to other APIs in the Sinch product family. The numbers API can be used in tandem with any of our APIs that perform messaging or calling.","includeFiles":["docs/numbers/**/*.md","docs/numbers/**/*.yaml"],"excludeFiles":["docs/numbers/index.md"]},{"title":"Conversation API","description":"Send and receive messages globally on many popular channels with ease and confidence when using Sinch's Conversation API. Conversation API is the preferred API for sending mobile messages on SMS and other social channels with Sinch. It is a simple API with unified error messages, consistent request payloads, and common webhook payloads that are channel-agnostic.","includeFiles":["docs/conversation/**/*.md","docs/conversation/**/*.yaml"],"excludeFiles":["docs/conversation/index.md"]},{"title":"Voice API","description":"The Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice/**/*.md","docs/voice/**/*.yaml"],"excludeFiles":["docs/voice/index.md"]},{"title":"Voice API v2","description":"The Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice-2.0/**/*.md","docs/voice-2.0/**/*.yaml"],"excludeFiles":["docs/voice-2.0/index.md"]},{"title":"Verification API","description":"The Verification API is a platform for phone number verification. It consists of the API and different software development kits (the Sinch SDKs) that you integrate with your smartphone or web application and cloud based back-end services. Together they enable SMS, Flashcall, Phone Call and Data verification in your application.","includeFiles":["docs/verification/**/*.md","docs/verification/**/*.yaml"],"excludeFiles":["docs/verification/index.md"]},{"title":"Provisioning API","description":"Provisioning API allows you to programmatically set up your senders, accounts and templates on your favorite messaging platforms on the Conversation API. For now, you can create your first WhatsApp channel through Meta's Embedded sign up, you can configure your first SMS App and configure your webhooks. As development continues, we will be adding the most commonly used channels.","includeFiles":["docs/provisioning-api/**/*.md","docs/provisioning-api/**/*.json"],"excludeFiles":["docs/provisioning-api/index.md"]},{"title":"Elastic SIP Trunking API","description":"With Elastic SIP Trunking you can create and manage your SIP trunks and phone numbers programmatically.","includeFiles":["docs/est/**/*.md","docs/est/**/*.yaml"],"excludeFiles":["docs/est/index.md"]},{"title":"Fax API","description":"Send and receive HIPAA compliant faxes on our modern fax platform using our developer-friendly API.","includeFiles":["docs/fax/**/*.md","docs/fax/**/*.yaml"],"excludeFiles":["docs/fax/index.md"]},{"title":"In-app Voice and Video SDK","description":"The In-app Voice and Video SDK enables you to add voice and video calling capabilities directly into your mobile or web application using the Sinch SDKs.","includeFiles":["docs/in-app-calling/**/*.md"],"excludeFiles":["docs/in-app-calling/index.md"]},{"title":"Number Lookup API","description":"The Number Lookup API is designed to provide in-depth information about phone numbers, helping enterprises enhance their communication strategies and prevent fraud. By identifying the type of phone line (for example, mobile, landline, VoIP) and the associated carrier, the API allows businesses to optimize routing, reduce unnecessary costs, and improve customer engagement.","includeFiles":["docs/number-lookup-api-v2/**/*.md","docs/number-lookup-api-v2/**/*.yaml"],"excludeFiles":["docs/number-lookup-api-v2/index.md"]},{"title":"Functions","description":"Serverless compute for voice and messaging. Deploy your code and Sinch routes live calls and messages to it — no infrastructure to run.","includeFiles":["docs/functions/functions/**/*.md","docs/functions/concepts/**/*.md","docs/functions/reference/**/*.md"],"excludeFiles":["docs/functions/functions/index.md"]},{"title":"CLI","description":"One command line for every Sinch API and the full Functions lifecycle — scaffold, run locally, deploy, and manage.","includeFiles":["docs/functions/cli/**/*.md"],"excludeFiles":["docs/functions/cli/index.md"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"call-pacing-batch-calls","__idx":0},"children":["Call Pacing (Batch Calls)"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Voice API v2 supports ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["batch calling"]}," for any scenario where you dial many recipients with the same call flow, such as appointment reminders, outage notifications, outbound campaigns, or survey waves. Instead of hand-writing one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," request per recipient, you drive the whole list from a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["single request"]},". This tutorial uses ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["two destinations"]}," to keep things concrete. The request carries:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," array describing the call flow (with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@placeholder"]}," references)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," array, one object per destination, that fills the placeholders for that destination's call."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions"]}," object that controls ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["pacing"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]}," (max calls per second) and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ttlSeconds"]}," (how long the platform keeps trying to start queued calls)."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Providing ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," puts the request into ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["batch mode"]},"; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions"]}," are only valid in that mode. The request returns a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]},", plus a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," you use to poll progress or stop the batch. Sinch initiates calls in the background, honoring ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]}," so you don't overload your trunk or trip carrier rate limits."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"setup","__idx":2},"children":["Setup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Export your credentials and the two destinations into the shell you'll run the examples from. Nothing here reads a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," file; the examples pick everything up from the environment."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"export PROJECT_ID=\"your-project-id\"\nexport KEY_ID=\"your-access-key-id\"\nexport KEY_SECRET=\"your-access-key-secret\"\nexport SINCH_NUMBER=\"+1XXXXXXXXXX\"\n\n# The two destinations to dial (E.164)\nexport DEST_1=\"+15551110001\"\nexport DEST_2=\"+15551110002\"\n\n# Optional pacing (defaults shown)\nexport MAX_CPS=5\nexport TTL_SECONDS=1800\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Variable"},"children":["Variable"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Required"},"children":["Required"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Where to get it"},"children":["Where to get it"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PROJECT_ID"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com"},"children":["Sinch Dashboard"]}," → your Voice project"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Dashboard → Access Keys"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTP Basic username"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_SECRET"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Dashboard → Access Keys (shown once at creation)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTP Basic password"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SINCH_NUMBER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Dashboard → Numbers (a number assigned to the project)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Caller ID (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["from"]},")"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DEST_1"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your own list"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["First destination, E.164"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DEST_2"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your own list"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Second destination, E.164"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MAX_CPS"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["optional"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["n/a"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5"]}," in the examples"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TTL_SECONDS"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["optional"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["n/a"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["1800"]}," in the examples"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Dependencies:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["curl"]}," (and optionally ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["jq"]}," for pretty output) for the shell example."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install requests"]}," for the Python example."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Node 18+"]}," for the Node example (uses built-in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["crypto.randomUUID"]},")."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-a-batch-call","__idx":3},"children":["Create a batch call"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A batch is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["one request"]}," with ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," array"]},". Each entry in that array queues one call and supplies the placeholder values for it, so the two destinations here go out as a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]},", not two."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"bash--curl","__idx":4},"children":["Bash + curl"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/bin/bash\n# Submit one batch request containing both destinations, with pacing controls.\nset -e\n\n: \"${PROJECT_ID:?ERROR: PROJECT_ID is not set.}\"\n: \"${KEY_ID:?ERROR: KEY_ID is not set.}\"\n: \"${KEY_SECRET:?ERROR: KEY_SECRET is not set.}\"\n: \"${SINCH_NUMBER:?ERROR: SINCH_NUMBER is not set.}\"\n: \"${DEST_1:?ERROR: DEST_1 is not set.}\"\n: \"${DEST_2:?ERROR: DEST_2 is not set.}\"\n\nBASE_URL=\"https://voice.api.sinch.com/v2\"\nMAX_CPS=\"${MAX_CPS:-5}\"\nTTL_SECONDS=\"${TTL_SECONDS:-1800}\"\n\nBODY=$(printf '{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"batch-reminder\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"@toNumber\" } },\n      \"dialTimeoutDurationSeconds\": 30,\n      \"maxCallDurationSeconds\": 120,\n      \"events\": {\n        \"onAnswer\": [\n          {\n            \"command\": \"messages\",\n            \"messages\": [\n              { \"type\": \"SAY\",\n                \"say\": { \"text\": \"Hello, this is an automated reminder from Sinch. Goodbye.\",\n                         \"voiceName\": \"Emma\" } }\n            ],\n            \"events\": { \"onFinish\": [ { \"command\": \"hangup\" } ] }\n          }\n        ],\n        \"onHangup\": [ { \"command\": \"hangup\" } ]\n      }\n    }\n  ],\n  \"parameters\": [\n    { \"toNumber\": \"%s\" },\n    { \"toNumber\": \"%s\" }\n  ],\n  \"batchOptions\": {\n    \"maxCps\": %d,\n    \"ttlSeconds\": %d\n  }\n}' \"${SINCH_NUMBER}\" \"${DEST_1}\" \"${DEST_2}\" \"${MAX_CPS}\" \"${TTL_SECONDS}\")\n\nIDEMPOTENCY_KEY=\"$(command -v uuidgen >/dev/null && uuidgen || date +%s%N)\"\n\necho \"Submitting batch to ${DEST_1}, ${DEST_2} (maxCps=${MAX_CPS}, ttlSeconds=${TTL_SECONDS}) ...\"\n\nRESPONSE=$(curl -s -w \"\\n%{http_code}\" \\\n  -X POST \\\n  -u \"${KEY_ID}:${KEY_SECRET}\" \\\n  \"${BASE_URL}/projects/${PROJECT_ID}/calls\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: ${IDEMPOTENCY_KEY}\" \\\n  -d \"${BODY}\")\n\nHTTP_BODY=$(echo \"${RESPONSE}\" | head -n -1)\nHTTP_CODE=$(echo \"${RESPONSE}\" | tail -n 1)\n\nif [ \"${HTTP_CODE}\" -eq 201 ]; then\n  echo \"Batch created (HTTP ${HTTP_CODE}):\"\n  echo \"${HTTP_BODY}\" | (command -v jq > /dev/null && jq '.' || cat)\nelse\n  echo \"ERROR: API returned HTTP ${HTTP_CODE}:\" >&2\n  echo \"${HTTP_BODY}\" >&2\n  exit 1\nfi\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python","__idx":5},"children":["Python"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Submit one batch request containing both destinations, with pacing controls.\n# Requires: pip install requests\n\nimport json\nimport os\nimport sys\nimport uuid\n\nimport requests\n\nPROJECT_ID   = os.environ.get(\"PROJECT_ID\")\nKEY_ID       = os.environ.get(\"KEY_ID\")\nKEY_SECRET   = os.environ.get(\"KEY_SECRET\")\nSINCH_NUMBER = os.environ.get(\"SINCH_NUMBER\")\n\nrequired = {\n    \"PROJECT_ID\": PROJECT_ID, \"KEY_ID\": KEY_ID, \"KEY_SECRET\": KEY_SECRET,\n    \"SINCH_NUMBER\": SINCH_NUMBER,\n    \"DEST_1\": os.environ.get(\"DEST_1\"), \"DEST_2\": os.environ.get(\"DEST_2\"),\n}\nmissing = [k for k, v in required.items() if not v]\nif missing:\n    print(f\"ERROR: missing env vars: {', '.join(missing)}\", file=sys.stderr)\n    sys.exit(1)\n\nrecipients  = [os.environ[\"DEST_1\"], os.environ[\"DEST_2\"]]\nmax_cps     = int(os.environ.get(\"MAX_CPS\", \"5\"))\nttl_seconds = int(os.environ.get(\"TTL_SECONDS\", \"1800\"))\n\nurl = f\"https://voice.api.sinch.com/v2/projects/{PROJECT_ID}/calls\"\n\npayload = {\n    \"commands\": [\n        {\n            \"command\": \"dial\",\n            \"callName\": \"batch-reminder\",\n            \"from\": {\"type\": \"PHONE\", \"phone\": {\"number\": SINCH_NUMBER}},\n            \"to\":   {\"type\": \"PHONE\", \"phone\": {\"number\": \"@toNumber\"}},\n            \"dialTimeoutDurationSeconds\": 30,\n            \"maxCallDurationSeconds\": 120,\n            \"events\": {\n                \"onAnswer\": [\n                    {\n                        \"command\": \"messages\",\n                        \"messages\": [\n                            {\"type\": \"SAY\",\n                             \"say\": {\"text\": \"Hello, this is an automated reminder from Sinch. Goodbye.\",\n                                     \"voiceName\": \"Emma\"}}\n                        ],\n                        \"events\": {\"onFinish\": [{\"command\": \"hangup\"}]}\n                    }\n                ],\n                \"onHangup\": [{\"command\": \"hangup\"}]\n            }\n        }\n    ],\n    # `parameters` is an array; each entry queues one call using that\n    # entry's placeholder values. One request, however many destinations.\n    \"parameters\": [{\"toNumber\": recipient} for recipient in recipients],\n    \"batchOptions\": {\"maxCps\": max_cps, \"ttlSeconds\": ttl_seconds},\n}\n\nheaders = {\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": str(uuid.uuid4()),\n}\n\nprint(f\"Submitting batch to {', '.join(recipients)} (maxCps={max_cps}, ttlSeconds={ttl_seconds}) ...\")\n\nresponse = requests.post(url, json=payload, headers=headers, auth=(KEY_ID, KEY_SECRET))\ndata = response.json()\n\nif response.status_code == 201:\n    print(\"Batch created:\")\n    print(json.dumps(data, indent=2))\nelse:\n    print(f\"ERROR {response.status_code}:\", file=sys.stderr)\n    print(json.dumps(data, indent=2), file=sys.stderr)\n    sys.exit(1)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs-18","__idx":6},"children":["Node.js (18+)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Submit one batch request containing both destinations, with pacing controls.\n// Node.js 18+ (built-in fetch and crypto.randomUUID).\n\nimport { randomUUID } from \"crypto\";\n\nconst required = [\"PROJECT_ID\", \"KEY_ID\", \"KEY_SECRET\", \"SINCH_NUMBER\", \"DEST_1\", \"DEST_2\"];\nfor (const name of required) {\n  if (!process.env[name]) {\n    console.error(`ERROR: ${name} is not set. Run the export commands above.`);\n    process.exit(1);\n  }\n}\n\nconst { PROJECT_ID, KEY_ID, KEY_SECRET, SINCH_NUMBER } = process.env;\nconst recipients = [process.env.DEST_1, process.env.DEST_2];\nconst maxCps     = Number(process.env.MAX_CPS     || 5);\nconst ttlSeconds = Number(process.env.TTL_SECONDS || 1800);\n\nconst url = `https://voice.api.sinch.com/v2/projects/${PROJECT_ID}/calls`;\nconst authHeader = \"Basic \" + Buffer.from(`${KEY_ID}:${KEY_SECRET}`).toString(\"base64\");\n\nconst payload = {\n  commands: [\n    {\n      command: \"dial\",\n      callName: \"batch-reminder\",\n      from: { type: \"PHONE\", phone: { number: SINCH_NUMBER } },\n      to:   { type: \"PHONE\", phone: { number: \"@toNumber\" } },\n      dialTimeoutDurationSeconds: 30,\n      maxCallDurationSeconds: 120,\n      events: {\n        onAnswer: [\n          {\n            command: \"messages\",\n            messages: [\n              { type: \"SAY\",\n                say: { text: \"Hello, this is an automated reminder from Sinch. Goodbye.\",\n                       voiceName: \"Emma\" } }\n            ],\n            events: { onFinish: [{ command: \"hangup\" }] }\n          }\n        ],\n        onHangup: [{ command: \"hangup\" }]\n      }\n    }\n  ],\n  // `parameters` is an array; each entry queues one call using that\n  // entry's placeholder values. One request, however many destinations.\n  parameters: recipients.map((toNumber) => ({ toNumber })),\n  batchOptions: { maxCps, ttlSeconds }\n};\n\nconsole.log(`Submitting batch to ${recipients.join(\", \")} (maxCps=${maxCps}, ttlSeconds=${ttlSeconds}) ...`);\n\nconst response = await fetch(url, {\n  method: \"POST\",\n  headers: {\n    Authorization: authHeader,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify(payload),\n});\n\nconst data = await response.json();\n\nif (response.status === 201) {\n  console.log(\"Batch created:\");\n  console.log(JSON.stringify(data, null, 2));\n} else {\n  console.error(`ERROR ${response.status}:`);\n  console.error(JSON.stringify(data, null, 2));\n  process.exit(1);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"what-success-looks-like","__idx":7},"children":["What success looks like"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," request returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["201 Created"]}," with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]}," and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," covering both destinations:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"projectId\": \"5c5bf2b1-35ae-4825-ab89-457e07bb60e6\",\n  \"serviceId\": \"6e124178-c29d-46a5-943c-5c2ae544aade\",\n  \"sessionId\": \"01BX5ZZKBKACTAV9WEVGEMMVRB\",\n  \"batchId\":   \"01BX5ZZKBKACTAV9WEVGEMMVRC\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," is included only because ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," was provided (batch mode). This top-level ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]}," identifies the request itself, not any one destination's call. To see the session created for each destination individually, use the batch details endpoint below."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"poll-until-a-batch-finishes","__idx":8},"children":["Poll until a batch finishes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You have one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," covering both destinations. Poll it with the loop below, which reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PROJECT_ID"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_SECRET"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BATCH_ID"]}," from the environment and prints the summary every 5 seconds until the batch reaches a final state."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"export BATCH_ID=01BX5ZZKBKACTAV9WEVGEMMVRC\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/bin/bash\n# Poll a batch summary every 5s until all call sessions reach a final state.\nset -e\n\n: \"${PROJECT_ID:?ERROR: PROJECT_ID is not set.}\"\n: \"${KEY_ID:?ERROR: KEY_ID is not set.}\"\n: \"${KEY_SECRET:?ERROR: KEY_SECRET is not set.}\"\n: \"${BATCH_ID:?ERROR: BATCH_ID is not set. Run: export BATCH_ID=01...}\"\n\nURL=\"https://voice.api.sinch.com/v2/projects/${PROJECT_ID}/batches/${BATCH_ID}\"\n\nwhile true; do\n  BODY=$(curl -s -u \"${KEY_ID}:${KEY_SECRET}\" \"${URL}\")\n  if command -v jq > /dev/null; then\n    echo \"$BODY\" | jq '{batchId, sessionCount, completed, expired, inProgress, queued, endTime}'\n    DONE=$(echo \"$BODY\" | jq -r 'if .queued==0 and .inProgress==0 then \"yes\" else \"no\" end')\n  else\n    echo \"$BODY\"\n    DONE=\"no\"\n  fi\n  if [ \"$DONE\" = \"yes\" ]; then\n    echo \"All call sessions finished.\"\n    break\n  fi\n  sleep 5\ndone\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With two destinations queued, the summary looks like:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"batchId\":       \"01BX5ZZKBKACTAV9WEVGEMMVRC\",\n  \"sessionCount\":  2,\n  \"queued\":        0,\n  \"inProgress\":    0,\n  \"completed\":     2,\n  \"expired\":       0,\n  \"requestedCps\":  5,\n  \"ttlSeconds\":    1800,\n  \"endTime\":       \"2025-02-10T09:05:00Z\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["queued"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inProgress"]}," both reach ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["endTime"]}," is set and the poll loop exits."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-it-works","__idx":9},"children":["How it works"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-placeholders","__idx":10},"children":["1. Placeholders"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," is a normal SVAML payload, except any string value can reference a parameter with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@name"]}," syntax. For each entry in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," array, the API queues one call and replaces each placeholder with that entry's value before initiating it."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"reminder\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"+1XXXXXXXXXX\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"@toNumber\" } },\n      \"dialTimeoutDurationSeconds\": 30,\n      \"events\": {\n        \"onAnswer\": [\n          {\n            \"command\": \"messages\",\n            \"messages\": [\n              { \"type\": \"SAY\",\n                \"say\": { \"text\": \"Hi @firstName, this is a reminder that your appointment is on @date.\",\n                         \"voiceName\": \"Emma\" } }\n            ],\n            \"events\": { \"onFinish\": [ { \"command\": \"hangup\" } ] }\n          }\n        ],\n        \"onHangup\": [ { \"command\": \"hangup\" } ]\n      }\n    }\n  ],\n  \"parameters\": [\n    { \"toNumber\": \"+15551110001\", \"firstName\": \"Ada\",  \"date\": \"Tuesday March 5\" },\n    { \"toNumber\": \"+15551110002\", \"firstName\": \"Grace\", \"date\": \"Wednesday March 6\" }\n  ],\n  \"batchOptions\": {\n    \"maxCps\": 5,\n    \"ttlSeconds\": 1800\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," is an array of objects (up to 10,000 keys each, key names 1-32 chars, string values only). Each object is referenced in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@key"]}," and queues exactly one call, personalized with that object's values, so this single request queues both destinations with their own ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["firstName"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["date"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-pacing--batchoptions-","__idx":11},"children":["2. Pacing (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions"]},")"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Default"},"children":["Default"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Meaning"},"children":["Meaning"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer (1-1000)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1000"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Requested maximum call-initiation rate, in calls per second. Actual CPS may be lower depending on routing, carrier capacity, and account limits."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ttlSeconds"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer (1-10800)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["10800 (3 h)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["How long the platform keeps trying to start queued calls. After TTL expires, queued (not-yet-initiated) call sessions are marked ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["EXPIRED"]},"; in-progress calls continue."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pick ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]}," based on what your trunk and downstream agent pool can handle; pick ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ttlSeconds"]}," based on how time-sensitive the message is."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-monitor-batch-progress","__idx":12},"children":["3. Monitor batch progress"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v2/projects/{projectId}/batches/{batchId}"]}," returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchSummary"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -u \"$KEY_ID:$KEY_SECRET\" \\\n  \"https://voice.api.sinch.com/v2/projects/$PROJECT_ID/batches/$BATCH_ID\" | jq\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"batchId\":      \"01BX5ZZKBKACTAV9WEVGEMMVRC\",\n  \"sessionCount\": 2,\n  \"queued\":       1,\n  \"inProgress\":   1,\n  \"completed\":    0,\n  \"expired\":      0,\n  \"requestedCps\": 5,\n  \"ttlSeconds\":   1800,\n  \"endTime\":      null\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Note the field is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requestedCps"]},", not ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionCount"]}," is the number of call sessions queued from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," array. There's no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}," counter here: sessions that never got a chance to start show up under ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expired"]}," once the TTL passes; the eventual outcome of a session that did start (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["BUSY"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NO_ANSWER"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FAILED"]},", and so on) is visible per-call via the session endpoint below, not in this summary. The summary is populated incrementally; poll on a sensible cadence (for example, every 10 s for short batches, once a minute for hour-long ones)."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a session-by-session view instead of counts, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v2/projects/{projectId}/batches/{batchId}/details"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -u \"$KEY_ID:$KEY_SECRET\" \\\n  \"https://voice.api.sinch.com/v2/projects/$PROJECT_ID/batches/$BATCH_ID/details\" | jq\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"sessions\": [\n    { \"id\": \"01F8Z5J4X2G9Y3J4X2G9Y3J4X2G9\", \"status\": \"COMPLETED\" },\n    { \"id\": \"01F8Z5J4X2G9Y3J4X2G9Y3J4X5YJ\", \"status\": \"IN_PROGRESS\" }\n  ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," is a session identifier, one per entry in the original ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," array; its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," is one of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["QUEUED"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IN_PROGRESS"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["COMPLETED"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["EXPIRED"]},". Use these ids with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/projects/{projectId}/sessions/{sessionId}"]}," to see how each individual destination's call actually ended."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-stop-the-batch-optional","__idx":13},"children":["4. Stop the batch (optional)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X DELETE -u \"$KEY_ID:$KEY_SECRET\" \\\n  \"https://voice.api.sinch.com/v2/projects/$PROJECT_ID/batches/$BATCH_ID\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DELETE /v2/projects/{projectId}/batches/{batchId}"]}," returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["202 Accepted"]}," with a JSON body confirming the stop:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{ \"result\": \"Batch calls stopped successfully, unprocessed calls will not be initiated\" }\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["It stops any queued (not-yet-initiated) call sessions in the batch. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Calls already in progress are not affected"]},"; they continue until they end naturally. Since one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," now covers every destination in the request, a single call to this endpoint stops the whole batch."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"5-inspect-individual-sessions","__idx":14},"children":["5. Inspect individual sessions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For each ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," returned by the batch details endpoint, fetch session details:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -u \"$KEY_ID:$KEY_SECRET\" \\\n  \"https://voice.api.sinch.com/v2/projects/$PROJECT_ID/sessions/$SESSION_ID\" | jq\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The response includes a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["calls[]"]}," array, one entry per call leg, with fields like ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callResult"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callReason"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startTime"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["answerTime"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["endTime"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/track-call-status"},"children":["Track Call Status"]}," for a deeper walkthrough."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"real-life-examples","__idx":15},"children":["Real-life examples"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Appointment reminders"]},": TTS reminder to every patient with an appointment tomorrow, as one batch request with one ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," entry per patient. Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps: 5"]}," so reminders trickle out over the morning instead of dialing 500 numbers in one second."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Outage notifications"]},": Notify all impacted customers in a region in a single request. Use a short ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ttlSeconds"]}," so calls that didn't reach anyone within the outage window aren't placed an hour later."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Survey waves"]},": Dispatch follow-up surveys after a release."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Dunning / collection campaigns"]},": Pace outbound attempts to comply with collection-call cadence regulations."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"production-readiness-checklist","__idx":16},"children":["Production-readiness checklist"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Concern"},"children":["Concern"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"What to do"},"children":["What to do"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Idempotency"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Always send the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}," header. A network retry of the whole batch request must not double-queue every destination. UUID v4 recommended; the key must be 16-128 chars. All three examples set one automatically per submission."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["CPS choice"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Pick ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCps"]}," based on trunk capacity AND any downstream agent pool. If your IVR bridges to live agents, sustained CPS above agent availability just produces abandoned calls."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TTL choice"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["For time-sensitive messages (outage alerts), use a TTL just slightly longer than the relevance window. For non-urgent batches (reminders), longer TTLs absorb temporary carrier slowness."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["AMD / voicemail"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Insert an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," command before the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["messages"]}," block when the recipient might be a mobile that goes to voicemail. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/amd"},"children":["AMD"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Recording compliance"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["If recordings are required, insert ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["startRecording"]}," inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]},". The recording is per-session. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/recording-and-transcription"},"children":["Recording & Transcription"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failure observability"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Persist the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," and, for each destination, its session ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," from the batch details endpoint to your CRM. After a batch ends, join each session id back to its destination to report which messages reached whom."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Per-recipient personalization"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Each object in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," fills the placeholders for exactly one queued call, in the same order the entries are listed. Add one entry per destination in a single request rather than sending separate requests."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," limits"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Each object supports up to 10,000 keys; key names are 1-32 characters, and values must be strings. Keep the array to the destinations for one batch and use separate batches for logically distinct campaigns."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"api-reference-at-a-glance","__idx":17},"children":["API reference (at a glance)"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]},": body is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callRequest"]},", made of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," (required) plus optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," plus optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions"]},". Providing ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," enables batch mode; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions"]}," are only valid then. Returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["201"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callResponse"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["projectId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["serviceId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]}," when batched)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parameters"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requestParameters"]},"): an ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["array"]}," of objects (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requestParameter"]},"), each a dictionary of string keys (1-32 chars, up to 10,000 keys) to string values, referenced in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@key"]},". Each array entry queues exactly one call, so a single request can queue an entire batch."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions.maxCps"]},": integer 1-1000, default 1000."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchOptions.ttlSeconds"]},": integer 1-10800, default 10800."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v2/projects/{projectId}/batches/{batchId}"]},": returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchSummary"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionCount"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["queued"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inProgress"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["completed"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expired"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requestedCps"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ttlSeconds"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["endTime"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v2/projects/{projectId}/batches/{batchId}/details"]},": returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchDetails"]}," with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessions[]"]}," array (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," in {",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["QUEUED"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IN_PROGRESS"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["COMPLETED"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["EXPIRED"]},"})."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DELETE /v2/projects/{projectId}/batches/{batchId}"]},": stops processing of queued call sessions in the batch; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["202 Accepted"]}," with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ \"result\": ... }"]}," body."]}]}]},"headings":[{"value":"Call Pacing (Batch Calls)","id":"call-pacing-batch-calls","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Setup","id":"setup","depth":2},{"value":"Create a batch call","id":"create-a-batch-call","depth":2},{"value":"Bash + curl","id":"bash--curl","depth":3},{"value":"Python","id":"python","depth":3},{"value":"Node.js (18+)","id":"nodejs-18","depth":3},{"value":"What success looks like","id":"what-success-looks-like","depth":3},{"value":"Poll until a batch finishes","id":"poll-until-a-batch-finishes","depth":2},{"value":"How it works","id":"how-it-works","depth":2},{"value":"1. Placeholders","id":"1-placeholders","depth":3},{"value":"2. Pacing ( batchOptions )","id":"2-pacing--batchoptions-","depth":3},{"value":"3. Monitor batch progress","id":"3-monitor-batch-progress","depth":3},{"value":"4. Stop the batch (optional)","id":"4-stop-the-batch-optional","depth":3},{"value":"5. Inspect individual sessions","id":"5-inspect-individual-sessions","depth":3},{"value":"Real-life examples","id":"real-life-examples","depth":2},{"value":"Production-readiness checklist","id":"production-readiness-checklist","depth":2},{"value":"API reference (at a glance)","id":"api-reference-at-a-glance","depth":2}],"frontmatter":{"seo":{"title":"Call Pacing (Batch Calls)"}},"lastModified":"2026-08-31T13:48:15.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/voice-2.0/tutorials/call-pacing","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}