{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/voice-2.0/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"product":"Voice v2 (preview)","type":"markdown"},"seo":{"title":"Make an Outbound Call","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":"make-an-outbound-call","__idx":0},"children":["Make an Outbound Call"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Sinch Voice API lets you programmatically dial any phone number from your project. You issue a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," request carrying SVAML (Sinch Voice Application Markup Language) commands that describe what happens during the call. For example: dial a phone number and, once the call is answered, play a recorded audio file, synthesize a Text-to-Speech message, then hang up."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The simplest outbound call is a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]}," command with an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]}," event that plays a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["messages"]}," block. No webhook server is needed because the full call flow is included inline in the request."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-youll-build","__idx":2},"children":["What you'll build"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In under five minutes you'll place a real outbound call that plays an audio clip and a Text-to-Speech message to a phone you control, then hangs up. You'll see an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HTTP 201"]}," response carrying a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]}," you can later use to track the call."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"real-life-examples","__idx":3},"children":["Real-life examples"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Appointment reminders"]},": \"Hello, this is a reminder that your dentist appointment is tomorrow at 2 PM.\""]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["One-time passcodes (OTP)"]},": \"Your verification code is 4 8 3 7. Do not share this code with anyone.\""]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Delivery notifications"]},": \"Your package has arrived at the locker. Your pickup code is 1 2 3 4.\""]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Outage alerts"]},": \"We are experiencing a service disruption in your area.\""]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"setup","__idx":4},"children":["Setup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before running anything, gather these values. You'll set them as environment variables with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export"]}," (covered in the next section)."]},{"$$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":"Where to find it"},"children":["Where to find it"]}]}]},{"$$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":["Your Sinch project UUID, shown in the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com"},"children":["Sinch dashboard"]},"."]}]},{"$$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":["API key ID. Create a key pair under ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Settings -> Access Keys"]}," in the dashboard. Used as the Basic Auth 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":["API key secret, shown once when you create the key pair. Used as the Basic Auth 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":["A Sinch virtual phone number assigned to your project, in E.164 format (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["+14045001000"]},"). Provision one under ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Numbers"]}," in the dashboard. Used as the caller ID."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The number you want to call, in E.164 format (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["+14155551234"]},"). For your first test, use a phone you can answer."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Authentication is HTTP Basic using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID:KEY_SECRET"]},". The API base URL is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://voice.api.sinch.com"]}," (regional hosts such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://us1.voice.api.sinch.com"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eu1"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["br1"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sg1"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["au1"]}," are also available)."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tooling per language:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Shell"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]}," (and optionally ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jq"]}," for pretty output)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install requests"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Node.js"]},": Node 18+ (built-in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]},"); no dependencies. Run as an ES module (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"type\": \"module\""]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]},", or use a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".mjs"]}," extension). On Node < 18, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install node-fetch"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["PHP"]},": PHP 8+ with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]}," extension."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Java"]},": JDK 11+ (uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["java.net.http.HttpClient"]},"); no external dependencies."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"first-success-place-a-call-now","__idx":5},"children":["First success: place a call now"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-export-your-credentials","__idx":6},"children":["1. Export your credentials"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each example reads its configuration from environment variables. Set them once in your shell with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export"]},". Because ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export"]}," marks the variables for the environment, any program you launch from that same shell (Python, Node, PHP, Java, or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]},") inherits them automatically."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"export PROJECT_ID=\"your-project-uuid\"\nexport KEY_ID=\"your-key-id\"\nexport KEY_SECRET=\"your-key-secret\"\nexport SINCH_NUMBER=\"+14045001000\"\nexport DESTINATION_NUMBER=\"+14155551234\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["These last only for the current shell session. Open a new terminal and you'll need to export them again. To make them persistent, add the lines to your shell profile (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["~/.bashrc"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["~/.zshrc"]},", or similar). On Windows PowerShell, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$env:PROJECT_ID = \"...\""]}," instead."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify they're set before continuing:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"echo \"$PROJECT_ID $SINCH_NUMBER $DESTINATION_NUMBER\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-place-the-call-pick-your-language","__idx":7},"children":["2. Place the call (pick your language)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run any of the examples below from the same shell where you exported the variables."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"shell-curl","__idx":8},"children":["Shell (curl)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Paste this directly into your terminal, or save it as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callout.sh"]}," and run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bash callout.sh"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/bin/bash\nset -e\n\n: \"${PROJECT_ID:?ERROR: PROJECT_ID is not set. Run: export PROJECT_ID=...}\"\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: \"${DESTINATION_NUMBER:?ERROR: DESTINATION_NUMBER is not set.}\"\n\necho \"Placing callout from ${SINCH_NUMBER} to ${DESTINATION_NUMBER} ...\"\n\nRESPONSE=$(curl -s -w \"\\n%{http_code}\" \\\n  -X POST \\\n  -u \"${KEY_ID}:${KEY_SECRET}\" \\\n  \"https://voice.api.sinch.com/v2/projects/${PROJECT_ID}/calls\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @- <<EOF\n{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"audio-notification\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"${SINCH_NUMBER}\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"${DESTINATION_NUMBER}\" } },\n      \"dialTimeoutDurationSeconds\": 30,\n      \"maxCallDurationSeconds\": 300,\n      \"events\": {\n        \"onAnswer\": [\n          {\n            \"command\": \"messages\",\n            \"messagesName\": \"notification\",\n            \"messages\": [\n              { \"type\": \"PLAY\", \"play\": { \"url\": \"https://samplelib.com/mp3/sample-12s.mp3\" } },\n              { \"type\": \"SAY\",  \"say\": {\n                  \"text\": \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\",\n                  \"voiceName\": \"Emma\"\n              } }\n            ],\n            \"events\": { \"onFinish\": [ { \"command\": \"hangup\" } ] }\n          }\n        ]\n      }\n    }\n  ]\n}\nEOF\n)\n\nHTTP_BODY=$(echo \"${RESPONSE}\" | head -n -1)\nHTTP_CODE=$(echo \"${RESPONSE}\" | tail -n 1)\n\nif [ \"${HTTP_CODE}\" -eq 201 ]; then\n  echo \"Call created successfully (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":4,"id":"python","__idx":9},"children":["Python"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callout.py"]},", then run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["python callout.py"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Requirements: pip install requests\nimport json\nimport os\nimport sys\n\nimport requests\n\n\ndef env(name):\n    value = os.environ.get(name)\n    if not value:\n        print(f\"ERROR: {name} is not set. Run: export {name}=...\", file=sys.stderr)\n        sys.exit(1)\n    return value\n\n\nproject_id         = env(\"PROJECT_ID\")\nkey_id             = env(\"KEY_ID\")\nkey_secret         = env(\"KEY_SECRET\")\nsinch_number       = env(\"SINCH_NUMBER\")\ndestination_number = env(\"DESTINATION_NUMBER\")\n\nurl = f\"https://voice.api.sinch.com/v2/projects/{project_id}/calls\"\n\n# SVAML payload: dial -> on answer play audio file then TTS -> hangup\npayload = {\n    \"commands\": [\n        {\n            \"command\": \"dial\",\n            \"callName\": \"audio-notification\",\n            \"from\": {\"type\": \"PHONE\", \"phone\": {\"number\": sinch_number}},\n            \"to\":   {\"type\": \"PHONE\", \"phone\": {\"number\": destination_number}},\n            \"dialTimeoutDurationSeconds\": 30,\n            \"maxCallDurationSeconds\": 300,\n            \"events\": {\n                \"onAnswer\": [\n                    {\n                        \"command\": \"messages\",\n                        \"messagesName\": \"notification\",\n                        \"messages\": [\n                            {\"type\": \"PLAY\", \"play\": {\"url\": \"https://samplelib.com/mp3/sample-12s.mp3\"}},\n                            {\"type\": \"SAY\", \"say\": {\n                                \"text\": \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\",\n                                \"voiceName\": \"Emma\",\n                            }},\n                        ],\n                        \"events\": {\"onFinish\": [{\"command\": \"hangup\"}]},\n                    }\n                ]\n            },\n        }\n    ]\n}\n\nprint(f\"Placing callout from {sinch_number} to {destination_number} ...\")\n\ntry:\n    response = requests.post(url, json=payload, auth=(key_id, key_secret))\n    data = response.json()\n\n    if response.status_code == 201:\n        print(\"Call created successfully:\")\n        print(json.dumps(data, indent=2))\n    else:\n        print(f\"ERROR {response.status_code}:\", file=sys.stderr)\n        print(json.dumps(data, indent=2), file=sys.stderr)\n        sys.exit(1)\nexcept requests.RequestException as e:\n    print(f\"Request failed: {e}\", file=sys.stderr)\n    sys.exit(1)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"nodejs","__idx":10},"children":["Node.js"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callout.mjs"]}," (the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".mjs"]}," extension enables ES module syntax), then run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node callout.mjs"]},". Uses the built-in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]}," from Node 18+; no dependencies."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"function env(name) {\n  const value = process.env[name];\n  if (!value) {\n    console.error(`ERROR: ${name} is not set. Run: export ${name}=...`);\n    process.exit(1);\n  }\n  return value;\n}\n\nconst projectId         = env(\"PROJECT_ID\");\nconst keyId             = env(\"KEY_ID\");\nconst keySecret         = env(\"KEY_SECRET\");\nconst sinchNumber       = env(\"SINCH_NUMBER\");\nconst destinationNumber = env(\"DESTINATION_NUMBER\");\n\nconst url = `https://voice.api.sinch.com/v2/projects/${projectId}/calls`;\n\n// Basic Auth: base64(\"keyId:keySecret\")\nconst authHeader = \"Basic \" + Buffer.from(`${keyId}:${keySecret}`).toString(\"base64\");\n\n// SVAML payload: dial -> on answer play audio file then TTS -> hangup\nconst payload = {\n  commands: [\n    {\n      command: \"dial\",\n      callName: \"audio-notification\",\n      from: { type: \"PHONE\", phone: { number: sinchNumber } },\n      to:   { type: \"PHONE\", phone: { number: destinationNumber } },\n      dialTimeoutDurationSeconds: 30,\n      maxCallDurationSeconds: 300,\n      events: {\n        onAnswer: [\n          {\n            command: \"messages\",\n            messagesName: \"notification\",\n            messages: [\n              { type: \"PLAY\", play: { url: \"https://samplelib.com/mp3/sample-12s.mp3\" } },\n              { type: \"SAY\", say: {\n                  text: \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\",\n                  voiceName: \"Emma\",\n              } },\n            ],\n            events: { onFinish: [ { command: \"hangup\" } ] },\n          },\n        ],\n      },\n    },\n  ],\n};\n\nconsole.log(`Placing callout from ${sinchNumber} to ${destinationNumber} ...`);\n\nconst response = await fetch(url, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\", Authorization: authHeader },\n  body: JSON.stringify(payload),\n});\n\nconst data = await response.json();\n\nif (response.status === 201) {\n  console.log(\"Call created successfully:\", JSON.stringify(data, null, 2));\n} else {\n  console.error(`ERROR ${response.status}:`, JSON.stringify(data, null, 2));\n  process.exit(1);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"php","__idx":11},"children":["PHP"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callout.php"]},", then run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["php callout.php"]},". Requires PHP 8+ with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["curl"]}," extension."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"<?php\n// Requirements: PHP 8+ with the curl extension enabled.\n\nfunction env(string $name): string {\n    $value = getenv($name);\n    if ($value === false || $value === '') {\n        fwrite(STDERR, \"ERROR: {$name} is not set. Run: export {$name}=...\\n\");\n        exit(1);\n    }\n    return $value;\n}\n\n$projectId         = env('PROJECT_ID');\n$keyId             = env('KEY_ID');\n$keySecret         = env('KEY_SECRET');\n$sinchNumber       = env('SINCH_NUMBER');\n$destinationNumber = env('DESTINATION_NUMBER');\n\n$url = \"https://voice.api.sinch.com/v2/projects/{$projectId}/calls\";\n\n// SVAML payload: dial -> on answer play audio file then TTS -> hangup\n$payload = [\n    'commands' => [\n        [\n            'command'  => 'dial',\n            'callName' => 'audio-notification',\n            'from'     => ['type' => 'PHONE', 'phone' => ['number' => $sinchNumber]],\n            'to'       => ['type' => 'PHONE', 'phone' => ['number' => $destinationNumber]],\n            'dialTimeoutDurationSeconds' => 30,\n            'maxCallDurationSeconds'     => 300,\n            'events' => [\n                'onAnswer' => [\n                    [\n                        'command'      => 'messages',\n                        'messagesName' => 'notification',\n                        'messages' => [\n                            ['type' => 'PLAY', 'play' => ['url' => 'https://samplelib.com/mp3/sample-12s.mp3']],\n                            ['type' => 'SAY',  'say'  => [\n                                'text'      => 'Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.',\n                                'voiceName' => 'Emma',\n                            ]],\n                        ],\n                        'events' => ['onFinish' => [['command' => 'hangup']]],\n                    ],\n                ],\n            ],\n        ],\n    ],\n];\n\necho \"Placing callout from {$sinchNumber} to {$destinationNumber} ...\\n\";\n\n$ch = curl_init($url);\ncurl_setopt_array($ch, [\n    CURLOPT_POST           => true,\n    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],\n    CURLOPT_USERPWD        => \"{$keyId}:{$keySecret}\",\n    CURLOPT_POSTFIELDS     => json_encode($payload),\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$responseBody = curl_exec($ch);\n$httpCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n$curlError    = curl_error($ch);\ncurl_close($ch);\n\nif ($curlError) {\n    fwrite(STDERR, \"curl error: {$curlError}\\n\");\n    exit(1);\n}\n\n$data = json_decode($responseBody, true);\n\nif ($httpCode === 201) {\n    echo \"Call created successfully:\\n\";\n    echo json_encode($data, JSON_PRETTY_PRINT) . \"\\n\";\n} else {\n    fwrite(STDERR, \"ERROR {$httpCode}:\\n\");\n    fwrite(STDERR, json_encode($data, JSON_PRETTY_PRINT) . \"\\n\");\n    exit(1);\n}\n","lang":"php"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"java","__idx":12},"children":["Java"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Callout.java"]},", then compile and run:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"javac -d out Callout.java && java -cp out com.sinch.tutorials.ttscallout.Callout\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requires JDK 11+ (uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["java.net.http.HttpClient"]},"); no external dependencies."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"package com.sinch.tutorials.ttscallout;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.Base64;\n\npublic class Callout {\n\n    public static void main(String[] args) throws Exception {\n        String projectId         = requireEnv(\"PROJECT_ID\");\n        String keyId             = requireEnv(\"KEY_ID\");\n        String keySecret         = requireEnv(\"KEY_SECRET\");\n        String sinchNumber       = requireEnv(\"SINCH_NUMBER\");\n        String destinationNumber = requireEnv(\"DESTINATION_NUMBER\");\n\n        String url = \"https://voice.api.sinch.com/v2/projects/\" + projectId + \"/calls\";\n\n        // Basic Auth header: base64(\"keyId:keySecret\")\n        String credentials = Base64.getEncoder()\n                .encodeToString((keyId + \":\" + keySecret).getBytes());\n\n        // SVAML payload: dial -> on answer play audio file then TTS -> hangup\n        String body = String.format(\"\"\"\n            {\n              \"commands\": [\n                {\n                  \"command\": \"dial\",\n                  \"callName\": \"audio-notification\",\n                  \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n                  \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n                  \"dialTimeoutDurationSeconds\": 30,\n                  \"maxCallDurationSeconds\": 300,\n                  \"events\": {\n                    \"onAnswer\": [\n                      {\n                        \"command\": \"messages\",\n                        \"messagesName\": \"notification\",\n                        \"messages\": [\n                          { \"type\": \"PLAY\", \"play\": { \"url\": \"https://samplelib.com/mp3/sample-12s.mp3\" } },\n                          { \"type\": \"SAY\",  \"say\": {\n                              \"text\": \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\",\n                              \"voiceName\": \"Emma\"\n                          } }\n                        ],\n                        \"events\": { \"onFinish\": [ { \"command\": \"hangup\" } ] }\n                      }\n                    ]\n                  }\n                }\n              ]\n            }\n            \"\"\", sinchNumber, destinationNumber);\n\n        System.out.println(\"Placing callout from \" + sinchNumber + \" to \" + destinationNumber + \" ...\");\n\n        HttpClient client = HttpClient.newHttpClient();\n        HttpRequest request = HttpRequest.newBuilder()\n                .uri(URI.create(url))\n                .header(\"Content-Type\", \"application/json\")\n                .header(\"Authorization\", \"Basic \" + credentials)\n                .POST(HttpRequest.BodyPublishers.ofString(body))\n                .build();\n\n        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n\n        if (response.statusCode() == 201) {\n            System.out.println(\"Call created successfully:\");\n            System.out.println(response.body());\n        } else {\n            System.err.println(\"ERROR \" + response.statusCode() + \":\");\n            System.err.println(response.body());\n            System.exit(1);\n        }\n    }\n\n    // Reads a variable from the environment (set it with `export NAME=...`).\n    private static String requireEnv(String name) {\n        String value = System.getenv(name);\n        if (value == null || value.isBlank()) {\n            System.err.println(\"ERROR: \" + name + \" is not set. Run: export \" + name + \"=...\");\n            System.exit(1);\n        }\n        return value;\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Browser note: a browser ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]}," version is also possible, but browsers have no environment variables, so ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export"]}," does not apply. You would inject the values at build time and, because calling the Sinch API directly from a browser hits CORS restrictions, route the request through your own backend proxy in production."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-what-success-looks-like","__idx":13},"children":["3. What success looks like"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The destination phone rings. When answered, it plays the sample audio clip followed by the TTS message \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\" then hangs up."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["On the console you'll see an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HTTP 201"]}," response:"]},{"$$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}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you get a non-201 status, check the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#troubleshooting"},"children":["Troubleshooting"]}," section below."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"reference","__idx":14},"children":["Reference"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"the-svaml-payload","__idx":15},"children":["The SVAML payload"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The request body sent to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," is:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"audio-notification\",\n      \"from\": {\n        \"type\": \"PHONE\",\n        \"phone\": { \"number\": \"+1XXXXXXXXXX\" }\n      },\n      \"to\": {\n        \"type\": \"PHONE\",\n        \"phone\": { \"number\": \"+1YYYYYYYYYY\" }\n      },\n      \"dialTimeoutDurationSeconds\": 30,\n      \"maxCallDurationSeconds\": 300,\n      \"events\": {\n        \"onAnswer\": [\n          {\n            \"command\": \"messages\",\n            \"messagesName\": \"notification\",\n            \"messages\": [\n              {\n                \"type\": \"PLAY\",\n                \"play\": { \"url\": \"https://samplelib.com/mp3/sample-12s.mp3\" }\n              },\n              {\n                \"type\": \"SAY\",\n                \"say\": {\n                  \"text\": \"Hello! This is a test notification from Sinch. Your verification code is 4 8 3 7.\",\n                  \"voiceName\": \"Emma\"\n                }\n              }\n            ],\n            \"events\": {\n              \"onFinish\": [ { \"command\": \"hangup\" } ]\n            }\n          }\n        ]\n      }\n    }\n  ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Key fields (cross-referenced with the OpenAPI spec):"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["command: \"dial\""]},": initiates a new outbound call leg. Non-blocking: the next command runs in parallel while the call is being established. Only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["command"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["to"]}," are required."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callName"]},": identifier for this call leg within the session (1 to 32 chars, no whitespace). Must be unique across active legs. Other commands (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hangup"]},") can reference it."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["from"]},": your Sinch virtual number, used as the caller ID. Requires a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}," discriminator. Supports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PHONE"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SIP"]}," only. Note that ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["from"]}," does ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," support ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["STREAM"]},"/",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["VOICE_RELAY"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["to"]},": the destination. Requires a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}," discriminator: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PHONE"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SIP"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["STREAM"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["VOICE_RELAY"]},". For PSTN this is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PHONE"]}," with an E.164 number."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dialTimeoutDurationSeconds"]},": how long to wait for the recipient to answer (integer, seconds). On expiry the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onTimeout"]}," event fires."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCallDurationSeconds"]},": hard ceiling on the duration of the answered call (integer, seconds). The call is terminated automatically when reached."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events.onAnswer"]},": SVAML commands executed once the call is answered. Other call lifecycle events available on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBusy"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onReject"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onTimeout"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHangup"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onFailure"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["command: \"messages\""]},": non-blocking; queues one or more messages and continues. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["messagesName"]}," (1 to 32 chars, no whitespace) names the sequence so it can be targeted by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stopMessages"]},". Each item is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SAY"]}," (TTS) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PLAY"]}," (audio URL)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events.onFinish"]}," on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["messages"]},": fires when all queued items have finished playing. Hanging up here ends the call cleanly."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Message item types"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}," discriminator):"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SAY"]},": requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["say.text"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["say.voiceName"]},". Optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["say.format"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TEXT"]}," (default) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SSML"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PLAY"]},": requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["play.url"]},", the URL of the media to play."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"handling-unanswered-and-failed-calls","__idx":16},"children":["Handling unanswered and failed calls"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The example above only handles the happy path (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]},"). A production flow should handle the other outcomes so a call that is never answered doesn't silently disappear. Add sibling handlers under the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]}," command's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"\"events\": {\n  \"onAnswer\":  [ { \"command\": \"messages\", \"messages\": [ /* ... */ ] } ],\n  \"onTimeout\": [ { \"command\": \"hangup\" } ],\n  \"onBusy\":    [ { \"command\": \"hangup\" } ],\n  \"onReject\":  [ { \"command\": \"hangup\" } ],\n  \"onFailure\": [ { \"command\": \"hangup\" } ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"safe-retries-with-an-idempotency-key","__idx":17},"children":["Safe retries with an Idempotency-Key"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createCall"]}," accepts an optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}," header (16 to 128 characters; a random UUID v4 is strongly recommended). If a request with the same key is received within 10 minutes, the server returns the cached response from the original request instead of placing a second call, which makes retries on network errors safe."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -H \"Idempotency-Key: $(uuidgen)\" ...\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Because a dropped connection can leave you unsure whether a call was actually created, ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["generate the key once before the first attempt and reuse the same key for every retry of that call."]}," Generating a new key per attempt defeats the purpose."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"the-response","__idx":18},"children":["The response"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A successful response is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HTTP 201"]}," with body:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"projectId\": \"...\",\n  \"serviceId\": \"...\",\n  \"sessionId\": \"...\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a batch of calls, the response additionally includes a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batchId"]},". Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]}," to inspect the call legs via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v2/projects/{projectId}/sessions/{sessionId}"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/track-call-status"},"children":["Track Call Status"]}," for details."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"troubleshooting","__idx":19},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createCall"]}," can return these error statuses; the response body describes the problem:"]},{"$$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":"Status"},"children":["Status"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Likely cause"},"children":["Likely cause"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Malformed SVAML (e.g. missing destination, invalid field)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Bad or missing Basic Auth credentials (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID"]},"/",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_SECRET"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["402"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Billing/payment issue on the account."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["403"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Credentials valid but not authorized for this project/action."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["404"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Unknown ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["projectId"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Rate limited. Back off and retry (reuse the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["500"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Server error. Retry with backoff."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If a script reports that a variable is not set, confirm you exported it in the same shell you're running from. Run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["echo \"$PROJECT_ID\""]}," to check, and remember that ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["export"]}," does not carry over to new terminal windows unless you added it to your shell profile."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"customise-the-messages","__idx":20},"children":["Customise the messages"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Swap the audio file"]},": replace ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["play.url"]}," with any publicly accessible MP3 or WAV:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{ \"type\": \"PLAY\", \"play\": { \"url\": \"https://example.com/your-audio.mp3\" } }\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Change the TTS voice or text"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["voiceName"]}," accepts values such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Emma"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Brian"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Reorder or remove messages"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["messages"]}," is a plain array; entries play sequentially:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"\"messages\": [\n  { \"type\": \"SAY\",  \"say\":  { \"text\": \"Welcome.\", \"voiceName\": \"Emma\" } },\n  { \"type\": \"PLAY\", \"play\": { \"url\": \"https://example.com/hold-music.mp3\" } }\n]\n","lang":"json"},"children":[]}]},"headings":[{"value":"Make an Outbound Call","id":"make-an-outbound-call","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"What you'll build","id":"what-youll-build","depth":2},{"value":"Real-life examples","id":"real-life-examples","depth":2},{"value":"Setup","id":"setup","depth":2},{"value":"First success: place a call now","id":"first-success-place-a-call-now","depth":2},{"value":"1. Export your credentials","id":"1-export-your-credentials","depth":3},{"value":"2. Place the call (pick your language)","id":"2-place-the-call-pick-your-language","depth":3},{"value":"Shell (curl)","id":"shell-curl","depth":4},{"value":"Python","id":"python","depth":4},{"value":"Node.js","id":"nodejs","depth":4},{"value":"PHP","id":"php","depth":4},{"value":"Java","id":"java","depth":4},{"value":"3. What success looks like","id":"3-what-success-looks-like","depth":3},{"value":"Reference","id":"reference","depth":2},{"value":"The SVAML payload","id":"the-svaml-payload","depth":3},{"value":"Handling unanswered and failed calls","id":"handling-unanswered-and-failed-calls","depth":3},{"value":"Safe retries with an Idempotency-Key","id":"safe-retries-with-an-idempotency-key","depth":3},{"value":"The response","id":"the-response","depth":3},{"value":"Troubleshooting","id":"troubleshooting","depth":3},{"value":"Customise the messages","id":"customise-the-messages","depth":3}],"frontmatter":{"seo":{"title":"Make an Outbound Call"}},"lastModified":"2026-09-01T08:14:37.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/voice-2.0/tutorials/outbound-tts","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}