# Quickstart

This guide walks you through placing your first outbound call with the Calls API. The call speaks a short message, so you do not need a web server yet.

## Before you begin

You need:

- Your Voice Account `AccountSid` and auth token. In the Signals console, open **Developers**, then **Voice Account**. Copy the `AccountSid`, then choose **Show** to see the auth token. Admins and engineers can view it at any time. Store it in a secrets manager.
- A phone number your Signals project owns, to use as the caller ID (`From`). See [Numbers and routing](/sip-trunks/numbers-and-routing).
- A Nigerian number to call (`To`). Your own phone works for testing.

:::caution
The auth token and a Signals API key are different credentials, even though both start with `sk_live_`. The Calls API accepts only the `AccountSid` and auth token. The Signals API at `/v1` accepts only API keys.
:::

## Make your first call

The following examples create an outbound call that speaks a greeting. The call script is passed inline with `Twiml`, so Signals does not need to request a URL on your server.

<CodeTabs syncKey="lang">

```bash title="curl"
curl -X POST "https://api.signals.pressone.africa/2010-04-01/Accounts/ACCOUNT_SID/Calls.json" \
  -u "ACCOUNT_SID:AUTH_TOKEN" \
  --data-urlencode "To=+2348031234567" \
  --data-urlencode "From=+2342013300100" \
  --data-urlencode "Twiml=<Response><Say>Hello from Signals. Your first call works.</Say></Response>"
```

```javascript title="Node.js"
const accountSid = process.env.SIGNALS_ACCOUNT_SID;
const authToken = process.env.SIGNALS_AUTH_TOKEN;

const params = new URLSearchParams({
  To: "+2348031234567",
  From: "+2342013300100",
  Twiml: "<Response><Say>Hello from Signals. Your first call works.</Say></Response>",
});

const response = await fetch(
  `https://api.signals.pressone.africa/2010-04-01/Accounts/${accountSid}/Calls.json`,
  {
    method: "POST",
    headers: {
      Authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: params,
  },
);

if (!response.ok) {
  throw new Error(`Create call failed: ${response.status} ${await response.text()}`);
}

const call = await response.json();
console.log(call.sid, call.status);
```

```python title="Python"
import os

import requests

account_sid = os.environ["SIGNALS_ACCOUNT_SID"]
auth_token = os.environ["SIGNALS_AUTH_TOKEN"]

response = requests.post(
    f"https://api.signals.pressone.africa/2010-04-01/Accounts/{account_sid}/Calls.json",
    auth=(account_sid, auth_token),
    data={
        "To": "+2348031234567",
        "From": "+2342013300100",
        "Twiml": "<Response><Say>Hello from Signals. Your first call works.</Say></Response>",
    },
    timeout=20,
)
response.raise_for_status()

call = response.json()
print(call["sid"], call["status"])
```

</CodeTabs>

<OpenPlaygroundButton
  server="https://api.signals.pressone.africa"
  url="/2010-04-01/Accounts/{AccountSid}/Calls.json"
  method="POST"
  pathParams={[{ name: "AccountSid", defaultValue: "ACCOUNT_SID" }]}
  headers={[{ name: "Content-Type", defaultValue: "application/x-www-form-urlencoded" }]}
  body={"To=%2B2348031234567&From=%2B2342013300100&Twiml=%3CResponse%3E%3CSay%3EHello%20from%20Signals.%3C%2FSay%3E%3C%2FResponse%3E"}
>
  Try it in the playground
</OpenPlaygroundButton>

## The response

A successful request returns `201` with the new call resource. The call starts in the `queued` status, and `direction` is `outbound-api` because it was created through the API:

```json
{
  "sid": "CA5388f7a5e04d0ed71cac5cdf1ea17a53",
  "account_sid": "AC9ce1cd445308e3ec5080c6126288aa09",
  "api_version": "2010-04-01",
  "date_created": "Thu, 02 Jul 2026 12:00:00 +0000",
  "date_updated": "Thu, 02 Jul 2026 12:00:00 +0000",
  "to": "+2348031234567",
  "to_formatted": "+2348031234567",
  "from": "+2342013300100",
  "from_formatted": "+2342013300100",
  "direction": "outbound-api",
  "status": "queued",
  "start_time": null,
  "end_time": null,
  "duration": null,
  "price": null,
  "price_unit": null,
  "queue_time": "0",
  "phone_number_sid": "PN9992109eed74a671cdc4e192a934d43e",
  "parent_call_sid": null,
  "annotation": null,
  "answered_by": null,
  "caller_name": null,
  "forwarded_from": null,
  "group_sid": null,
  "trunk_sid": null,
  "subresource_uris": {
    "recordings": "/2010-04-01/Accounts/AC9ce1cd445308e3ec5080c6126288aa09/Calls/CA5388f7a5e04d0ed71cac5cdf1ea17a53/Recordings.json",
    "events": "/2010-04-01/Accounts/AC9ce1cd445308e3ec5080c6126288aa09/Calls/CA5388f7a5e04d0ed71cac5cdf1ea17a53/Events.json"
  },
  "uri": "/2010-04-01/Accounts/AC9ce1cd445308e3ec5080c6126288aa09/Calls/CA5388f7a5e04d0ed71cac5cdf1ea17a53.json"
}
```

Save the `sid`. It identifies this call in every later request.

## Verify the result

Fetch the call resource to check its progress:

```bash
curl -u "ACCOUNT_SID:AUTH_TOKEN" \
  "https://api.signals.pressone.africa/2010-04-01/Accounts/ACCOUNT_SID/Calls/CA5388f7a5e04d0ed71cac5cdf1ea17a53.json"
```

<OpenPlaygroundButton
  server="https://api.signals.pressone.africa"
  url="/2010-04-01/Accounts/{AccountSid}/Calls/{CallSid}.json"
  method="GET"
  pathParams={[
    { name: "AccountSid", defaultValue: "ACCOUNT_SID" },
    { name: "CallSid", defaultValue: "CA5388f7a5e04d0ed71cac5cdf1ea17a53" },
  ]}
>
  Try it in the playground
</OpenPlaygroundButton>

The `status` field moves through these values as the call progresses:

| Status | Meaning |
|--------|---------|
| `queued` | The call was accepted and is waiting to be placed. |
| `ringing` | The destination phone is ringing. |
| `in-progress` | The call was answered and is connected. |
| `completed` | The call ended normally. |

If the destination does not answer within the ring timeout (60 seconds by default, set with the `Timeout` field), the status becomes `no-answer` instead. A busy line ends as `busy`, and a call that could not be placed ends as `failed`. Poll the call resource, or set a [status callback](/programmable-voice/status-callbacks) to be notified instead.

## Troubleshooting

### `401` with code `unauthenticated`

The `Authorization` header is missing, is not HTTP Basic, or its username is not the `AccountSid` in the path. Send exactly one Basic credential whose username matches the path.

### `401` with a plain-text or `20003` body

The username matched, but the auth token is wrong or was rotated. Use the current token from the console.

### `404` with code `voice_route_not_proxied`

The `AccountSid` is unknown or the Voice Account is not active yet, or the path is not one the Calls API serves. Check the `AccountSid` in the console and the path spelling, including the `.json` suffix on `Calls.json`.

### `400` with code `21210`

`From` is not a number your project owns. Use a number listed in the console under **Numbers**.

## Next steps

- [Receiving calls](/programmable-voice/receiving-calls): answer inbound calls and build a menu.
- [OneML overview](/programmable-voice/oneml/overview): every verb available for scripting a call.
- [Calls API reference](/api/voice-calls): every endpoint, parameter and error.
