REST how-to

Schedule an AI assistant call via REST — one POST, one webhook, one transcript.

Your CRM, ticketing tool, marketing automation or backend job sends one HTTP POST. The CodeB platform whitelists the target number, places a SIP call through your configured trunk, and attaches a Live Voice AI session that follows your system prompt. When the call ends, your webhook receiver gets a signed POST with the outcome and a transcript filename you can pull on demand. End to end — under twenty lines of code in any language.

REST / JSON Bearer auth HMAC-signed webhooks curl / Python / Node.js EU-hosted Multi-tenant Programmable retries

What you will build

A four-step integration: mint a token, POST a call, receive a webhook on completion, fetch the transcript. The diagram below shows where each component lives. Your code only ever talks to one host (your CodeB tenant) over plain HTTPS — no SIP, no WebRTC, no media plumbing on your side.

YOUR SYSTEM CRM / scheduler / backend Decides who to call, when, and what the AI should say CODEB TENANT /api.ashx/v1/calls Bearer-authenticated REST. Returns callId immediately. CONTACT Phone rings (PSTN / SIP) Voice AI greets, converses, transfers or hangs up. WEBHOOK PUSH outbound-ai.finished HMAC-signed POST to your URL with status, duration, reason. YOUR RECEIVER Updates CRM record Verifies X-CodeB-Signature, logs outcome, triggers next step. TRANSCRIPT FETCH GET /v1/transcripts/… Plain text + JSON sidecar with per-turn timestamps. 1. POST /v1/calls 2. SIP dial via trunk 3. Call ends 4. webhook POST 5. GET /v1/transcripts/<file> Solid amber arrows: outbound from your code. Dashed cyan arrows: platform pushes to you.

Prerequisites

A CodeB tenant

Any host you control: https://phone.yourtenant.com. Self-host or run on a CodeB-operated tenant. Multi-tenant isolation is per-domain.

A Bearer token

Either an OIDC access token (from /oidc.ashx/token) or an ak_-prefixed API key minted in the tenant admin. Either works on every endpoint on this page.

A configured SIP trunk

Already there if you can place / receive normal calls. The platform picks the trunk via your outbound routing rules — no per-call trunk choice needed.

A speech engine API key

Bring your own Voice AI engine key. You bring your own key; the platform never stores it across calls.

Recipient consent

Outbound AI calling is regulated. Confirm the target opted in (TCPA / GDPR / ePrivacy / local rules). The platform is a transport — consent is on you.

A webhook receiver (optional)

A public HTTPS endpoint that accepts POST. If you skip it, the call still happens — you just poll GET /v1/calls/{id} instead.

Step 1 — Mint a Bearer token

Skip this step if you already have an ak_-prefixed API key from the tenant admin — just use it directly as Authorization: Bearer ak_yourkeyhere. For interactive flows, exchange a username/password for a short-lived OIDC access token:

# Exchange username + password for a short-lived OIDC access token.
TOKEN=$(curl -s -X POST "https://phone.yourtenant.com/oidc.ashx/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&username=alice&password=YOUR_PASSWORD&scope=openid" \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

echo "$TOKEN"
Tip. For machine-to-machine jobs — a nightly cron, a webhook responder, a CRM workflow — mint an ak_-prefixed API key once in the tenant admin and skip the token dance entirely. It is the same Bearer header on every call.

Step 2 — POST the call

Send a single JSON body to POST /api.ashx/v1/calls with the destination number, the AI's instructions, and where to email the transcript. The response is immediate; the actual dial happens out of band.

Request fields

FieldTypeRequiredNotes
phonestringyesE.164, e.g. +4915157610183. Numbers are normalised before whitelist insertion.
displayNamestringnoCaller-ID label / SIP From-display. Defaults to phone.
emailstringyesRecipient for transcript + outcome email when the call ends.
systemPromptstringyesPlain-text instructions to the AI, or a slug of a saved tenant prompt (e.g. reminder).
apiKeystringyesVoice AI engine key. Used per call — not retained.
modelstringnoEngine model id. Defaults to the tenant configuration.
voicestringnoe.g. Aoede, Charon. Default Aoede.
languagestringnoBCP-47 tag, e.g. en-US, de-DE. Default en-US.
maxSecondsintnoHard cap, 10–3600. Default 300.
retriesintno0–10. How many times to redial on no-answer / busy. Default 0.
retryDelayMinutesintno1–1440. Gap between retry attempts. Default 5.
scheduleAtUtcstringnoISO-8601 UTC. If omitted, dial immediately. Use for future-time campaigns.

Example — appointment reminder

curl -X POST https://phone.yourtenant.com/api.ashx/v1/calls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone":        "+4915157610183",
    "displayName":  "Reminder",
    "email":        "ops@yourcompany.example",
    "systemPrompt": "You are calling Alex to confirm the dentist appointment tomorrow at 14:00. Be brief and warm. If they answer YES, say goodbye. If NO, offer to reschedule for the same time next week.",
    "apiKey":       "YOUR_AI_API_KEY",
    "voice":        "Aoede",
    "language":     "en-US",
    "maxSeconds":   180,
    "retries":      2,
    "retryDelayMinutes": 10
  }'

Response

{
  "ok":              true,
  "callId":          "oac-0123456789ab",
  "tenant":          "phone.yourtenant.com",
  "whitelistAdded":  true,
  "whitelistError":  null,
  "bridgeReply":     "{...}"
}

Persist the callId on your record. You will see it again on the webhook, in GET /v1/calls/{id}, and in the transcript filename (outbound-ai-YYYYMMDD-HHMMSS-<callId>.txt).

Step 3 — Receive the webhook

Within seconds of the call ending, your subscribed webhook URL gets two HMAC-signed POSTs: outbound-ai.finished with the outcome, and (if recording or transcription was enabled) transcript.saved with the path. Verify the signature first, always.

Verifying X-CodeB-Signature

import hmac, hashlib, os
from flask import Flask, request, abort

WEBHOOK_SECRET = os.environ["CODEB_WEBHOOK_SECRET"].encode()
app = Flask(__name__)

def verify(raw_body: bytes, sig_header: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, (sig_header or "").strip())

@app.post("/codeb/webhook")
def hook():
    raw = request.get_data()                        # raw bytes, NOT json()
    sig = request.headers.get("X-CodeB-Signature", "")
    if not verify(raw, sig):
        abort(401)
    evt = request.get_json()                        # now safe to parse
    if evt["type"] == "outbound-ai.finished":
        update_crm(evt["data"]["callId"], evt["data"]["status"])
    return ("", 204)

What the webhook body looks like

{
  "type":      "outbound-ai.finished",
  "tenant":    "phone.yourtenant.com",
  "timestamp": "2026-06-23T12:02:31.000Z",
  "data": {
    "callId":          "oac-0123456789ab",
    "phone":           "+4915157610183",
    "displayName":     "Reminder",
    "status":          "ended-success",
    "endedReason":     "finished",
    "durationSec":     145,
    "dispatchedAtUtc": "2026-06-23T12:00:00.000Z",
    "answeredAtUtc":   "2026-06-23T12:00:05.500Z",
    "endedAtUtc":      "2026-06-23T12:02:30.000Z",
    "transcriptPath":  "outbound-ai-20260623-120000-oac-0123456789ab.txt",
    "attempt":         1,
    "retriesLeft":     1
  }
}
Don't have a public URL? Skip the webhook entirely. Poll GET /api.ashx/v1/calls/{id} until status is one of ended-success, ended-failed-final, or cancelled. Cheap, no infrastructure, no signature dance.

Step 4 — Fetch the transcript

For analytics, follow-up generation, or compliance archiving, pull the transcript when the webhook arrives. The platform produces two artefacts per call: a plain-text transcript (.txt) and a structured JSON sidecar (.json) with per-turn timestamps, role labels, and tool-call records.

curl -H "Authorization: Bearer $TOKEN" \
  "https://phone.yourtenant.com/api.ashx/v1/transcripts/outbound-ai-20260623-120000-oac-0123456789ab.txt"

Sample transcript (plain text, one line per turn):

[12:00:06] AI:    Hello, this is your reminder service. Am I speaking with Alex?
[12:00:09] USER:  Yes, this is Alex.
[12:00:11] AI:    Just a quick reminder about your dentist appointment tomorrow at 14:00.
                  Will you still be able to make it?
[12:00:17] USER:  Yes, I'll be there.
[12:00:19] AI:    Wonderful. Have a great day. Goodbye.

Want recordings too? Enable per-vnum or per-trunk audio recording in the admin and a stereo WAV lands at App_Data/<tenant>/recordings/YYYY/MM/DD/ alongside a JSON sidecar with the same metadata. See REST API reference for the full transcripts + recordings endpoints.

Common patterns

Appointment reminders

Your scheduling tool fires the POST 24h before each booking. AI confirms or reschedules. Outcome lands back in your booking record via webhook.

Lead qualification

Marketing automation fires the POST seconds after form submission. AI greets, qualifies on a script, and (with permission) transfers to a sales rep mid-call.

Payment / dunning reminders

Billing job batches up overdue accounts each morning, POSTs one call per debtor. Tone-controlled by your system prompt. Transcript archived for the audit trail.

Service follow-up surveys

Ticketing tool POSTs an hour after ticket close. AI asks three structured questions. JSON transcript feeds your NPS dashboard.

Internal staff notifications

Oncall / outage tooling POSTs to your engineer's number. AI reads the alert, confirms acknowledgement, hangs up. Cheap, language-agnostic, no app to install.

Multi-language campaigns

Set language per call (en-US, de-DE, fr-FR, es-ES, …). Same endpoint, same flow, voice and prompt swap automatically.

Hospitality — sensors trigger calls

Short-let noise meter or smoke detector trips → sensor platform POSTs to your webhook → AI voice agent dials the guest in seconds, names the apartment, cites the threshold, asks them to act. Worked example on hospitality.html.

Cancel, list, inspect

Three more endpoints round out the lifecycle:

Verb + pathWhat it does
GET /api.ashx/v1/callsList active + recent calls. Filter by status: scheduled,in-flight,ended-success etc. Paginate with limit + offset.
GET /api.ashx/v1/calls/{id}One call — status, outcome, transcript path, attempt counter, retry budget.
POST /api.ashx/v1/calls/{id}/hangupCancel a scheduled call or terminate an in-flight one. Idempotent. Use when the contact replied via another channel before the AI dialed them.

Full request/response shape with every field is on the REST API reference.

Security & compliance

  • Bearer auth on every call. OIDC access tokens or ak_-prefixed API keys. No anonymous endpoints, no IP allowlists required for the REST surface.
  • HMAC-signed webhooks. X-CodeB-Signature is HMAC-SHA256 of the raw body with your subscription secret. Verify before parsing. Rotate the secret at any time from the admin.
  • Per-tenant isolation. Every API key, token, transcript, recording and CDR is scoped to the tenant host that issued it. Cross-tenant reads are blocked at the dispatcher.
  • EU-hosted, self-hostable. Run on your own infrastructure or on an EU-region tenant. No US transfer for call signalling, media, transcripts or recordings.
  • Recipient consent. Outbound AI calling is regulated in most jurisdictions (TCPA in the US, GDPR / ePrivacy in the EU, plus local rules). The platform places the call you ask it to; consent capture is your responsibility.
  • Auditable. Every call leaves a CDR (caller / callee / trunk / outcome / duration) and an optional transcript + recording for compliance retention.

Ready to ship outbound AI in an afternoon?

Spin up a free CodeB tenant, drop in your speech-engine API key, fire the first POST. Full reference + every endpoint is on the API hub.