telnyxdocs.com

Command Palette

Search for a command to run...

Configure a Call Control Application with the Telnyx REST API in TypeScript

Last updated: 10/6/2026

AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.

Configure a Call Control Application with the Telnyx REST API in TypeScript

A Telnyx call control application defines where voice webhooks are sent and how long Telnyx waits for your first command. This guide updates one with PATCH /v2/call_control_applications/{id} from a dependency-free TypeScript script and checks the result with a separate GET.

What you will build

A script that sets an application's name, primary and failover webhook URLs, webhook timeout, and first-command timeout, then reads the application back and compares every field it set.

PATCH /v2/call_control_applications/{id}  -> write the settings
GET   /v2/call_control_applications/{id}  -> read them back
compare desired vs stored                 -> exit 0 on match, exit 1 on mismatch

AI Prompt

Using the Telnyx REST API from TypeScript, update an existing call control application so it is named "inbound-ivr", sends webhooks to https://example.com/telnyx/voice with a failover at https://example.com/telnyx/voice-backup, uses a 10 second webhook timeout, and waits 15 seconds for the first command. Read the application back and fail if any field differs.

Requirements:
- Call https://api.telnyx.com/v2/call_control_applications/{id} with `fetch`, a `Bearer` token in the Authorization header, and a JSON body with `Content-Type: application/json`.
- Read the API key from TELNYX_API_KEY and the application id from TELNYX_CALL_CONTROL_APP_ID. Do not hardcode either.
- `webhook_timeout_secs` must be between 0 and 30. `first_command_timeout_secs` must be 0 or greater. `webhook_api_version` must be an accepted value, and "2" is accepted.
- Surface the `errors` array from non-2xx responses.
- Run the verification step below before finishing.

Prerequisites

  • Node.js 24 (the script is TypeScript and runs directly with node, without a build step or dependencies).
  • A Telnyx API key and the id of an existing call control application:
export TELNYX_API_KEY="<your Telnyx API key>"
export TELNYX_CALL_CONTROL_APP_ID="<your call control application id>"
  • To find the id, list your applications:
curl -s https://api.telnyx.com/v2/call_control_applications \
  -H "Authorization: Bearer $TELNYX_API_KEY"

Some account levels allow only one call control application. See Common issues.

Note: The API response for an application contains many more fields than the script prints. The output below is limited to the fields the script sets.

1. Save the script

Save this as call-control-app.ts:

const API = "https://api.telnyx.com/v2";
const apiKey = process.env.TELNYX_API_KEY;
const appId = process.env.TELNYX_CALL_CONTROL_APP_ID;
if (!apiKey || !appId) {
  throw new Error("Set TELNYX_API_KEY and TELNYX_CALL_CONTROL_APP_ID");
}

async function telnyx(method: string, path: string, body?: unknown) {
  const res = await fetch(`${API}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const json = await res.json();
  if (!res.ok) {
    throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json.errors)}`);
  }
  return json.data;
}

const desired = {
  application_name: "inbound-ivr",
  webhook_event_url: "https://example.com/telnyx/voice",
  webhook_event_failover_url: "https://example.com/telnyx/voice-backup",
  webhook_api_version: "2",
  webhook_timeout_secs: 10,
  first_command_timeout: true,
  first_command_timeout_secs: 15,
};

await telnyx("PATCH", `/call_control_applications/${appId}`, desired);

const app = await telnyx("GET", `/call_control_applications/${appId}`);
const mismatches = Object.entries(desired).filter(([key, value]) => app[key] !== value);

console.log(
  JSON.stringify(
    Object.fromEntries(Object.keys(desired).map((key) => [key, app[key]])),
    null,
    2,
  ),
);

if (mismatches.length > 0) {
  console.error("Mismatched fields:", mismatches.map(([key]) => key));
  process.exit(1);
}
console.log("All configured fields match.");

2. Run it

node call-control-app.ts

Real output:

{
  "application_name": "inbound-ivr",
  "webhook_event_url": "https://example.com/telnyx/voice",
  "webhook_event_failover_url": "https://example.com/telnyx/voice-backup",
  "webhook_api_version": "2",
  "webhook_timeout_secs": 10,
  "first_command_timeout": true,
  "first_command_timeout_secs": 15
}
All configured fields match.

The process exits with status 0.

Verify the result

The script's own GET is the check: it compares each field it set against the stored application and exits 1 on any difference. Run it again to confirm the result is stable:

node call-control-app.ts

Expected: the same seven fields as in step 2, followed by All configured fields match., with exit status 0.

How it works

PATCH applies only the fields in the request body, and the following GET returns the stored application. The script compares values with strict equality, so a number sent as 10 must come back as the number 10, and the string "2" must come back as a string.

Common issues

Validation errors return 422 with a field pointer

The API returns HTTP 422, error code 10015, and a source.pointer naming the field:

Request bodyDetail returned
{"webhook_event_url":"not-a-url"}You must specify http or https in your webhook URL(s)
{"webhook_timeout_secs":99}must be between 0 and 30
{"first_command_timeout_secs":-1}must be greater than or equal to 0
{"dtmf_type":"nope"}is not an acceptable value
{"anchorsite_override":"Mars"}is not an acceptable value
{"webhook_api_version":"3"}is not an acceptable value

Plain http webhook URLs are accepted

A PATCH with {"webhook_event_url":"http://example.com/x"} returned HTTP 200 and stored the URL. Use https for production webhooks.

A request without a JSON body does nothing

A PATCH sent with the body x=1 and no Content-Type: application/json header returned HTTP 200 with the application unchanged. Always send JSON, and always read the application back.

An unknown application id returns 404

GET /v2/call_control_applications/1234 returns HTTP 404, error code 10005, Resource not found. The script surfaces this as PATCH /call_control_applications/1234 -> 404: [...] and exits with an error.

Authentication failures return 401

A request without an Authorization header returns HTTP 401, error code 10009, with the detail Could not find any usable credentials in the request. A key that starts with KEY but is malformed (for example KEYmalformed) returns the same code with The API key looks malformed. Check that you copied it correctly. A value that does not start with KEY (for example not-a-real-key) returns the first message.

Creating an additional application can fail with an account-limit error

On the account used for this guide, POST /v2/call_control_applications returned HTTP 403, error code 10039, with the detail You may only have 1 Call Control Application(s) at your account level. Refer to https://telnyx.com/upgrade. Update the existing application instead.

Next steps

  • Verify the webhooks this application receives: 04-verify-telnyx-webhook-signatures-in-python.md.
  • Search for numbers to route to the application: 01-search-available-phone-numbers-with-the-telnyx-cli.md.

verification:
  status: verified
  tested_at: "2026-10-06"
  product_version: "Telnyx REST API v2, Node.js v24.18.0"
  command: "node call-control-app.ts"
  expected_result: "Seven configured fields printed with the values set by the script, followed by 'All configured fields match.', exit status 0."

Related Articles