phone.systemsby ODYGY

Developers

API Documentation

Build on top of phone.systems™ using a REST API. Provision numbers, configure call flows, place outbound calls, stream real-time events via webhooks, and fetch recordings and transcripts — all programmatically.

Overview

The phone.systems API is a RESTful JSON API. All requests are made over HTTPS to:

base url
https://api.odygy.com/v1

Requests and responses use application/json and UTF-8 encoding. Resource identifiers are opaque strings — treat them as such, don't parse them.

Authentication

The API uses bearer tokens. Generate an API key from the admin console under Settings → API keys and send it in the Authorization header of every request:

http
GET /v1/users HTTP/1.1
Host: api.odygy.com
Authorization: Bearer ps_live_abc123def456...
Accept: application/json
Keep your keys secret
API keys grant full access to your account. Never commit them to source control and never expose them in a client-side bundle. Rotate keys immediately if you think one has leaked.

Key scopes

Keys can be scoped to a subset of resources:

  • read:* — read-only access.
  • calls:write — place and control calls.
  • recordings:read — download recordings and transcripts.
  • webhooks:write — manage webhook subscriptions.

Conventions

  • Timestamps are ISO 8601 in UTC, e.g. 2026-04-23T10:12:45Z.
  • Phone numbers are in E.164 format, e.g. +40741234567.
  • Durations are expressed in seconds unless otherwise noted.
  • Money amounts are integers in the currency's smallest unit (cents for EUR/USD).
  • Unknown fields in request bodies are rejected with 400 Bad Request.

Pagination

List endpoints return at most 50 items per page and use cursor-based pagination. The response includes a next_cursor when more results are available:

json
{
  "data": [ { "id": "usr_01HZY..."}, ... ],
  "next_cursor": "eyJpZCI6ICJ1c3JfMDFI..."
}

Pass it as the cursor query parameter to fetch the next page. Use limit (max 100) to change the page size.

Errors

Errors use standard HTTP status codes and a machine-readable body:

json
{
  "error": {
    "code": "number_unavailable",
    "message": "The requested number is not available in this region.",
    "request_id": "req_01HZYABCDEF..."
  }
}
NameTypeDescription
400Bad RequestThe request was malformed or failed validation.
401UnauthorizedMissing or invalid API key.
403ForbiddenThe key lacks the required scope.
404Not FoundThe resource does not exist or was deleted.
409ConflictThe request conflicts with the current state (e.g. duplicate port request).
429Too Many RequestsYou exceeded the rate limit. See Rate limits.
5xxServer ErrorSomething went wrong on our side — retries with exponential backoff are recommended.

Rate limits

By default each API key is limited to 100 requests per second with a burst of 200, and to 20 concurrent outbound call creations. The current budget is exposed on every response:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 94
X-RateLimit-Reset: 1713869412

When the limit is exceeded the API returns 429 Too Many Requests with a Retry-After header telling you how many seconds to wait.

Accounts

GET/v1/account
Returns information about the authenticated account.
bash
curl https://api.odygy.com/v1/account \
  -H "Authorization: Bearer $ODYGY_API_KEY"
json
{
  "id": "acc_01HZY8ABCXYZ",
  "name": "Acme Ltd",
  "country": "RO",
  "currency": "EUR",
  "created_at": "2023-09-12T08:00:00Z"
}

Users

GET/v1/users
List all users in your account.
POST/v1/users
Create a user.
NameTypeDescription
namerequiredstringDisplay name.
emailrequiredstringMust be unique within the account. Used for login and notifications.
extensionrequiredstring3–6 digits. Must be unique within the account.
roleenumOne of admin, supervisor, agent. Defaults to agent.
caller_id_numberstringE.164 number used for outbound calls by default.
bash
curl -X POST https://api.odygy.com/v1/users \
  -H "Authorization: Bearer $ODYGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ivy Bennett",
    "email": "ivy@acme.io",
    "extension": "1244",
    "role": "agent"
  }'
GET/v1/users/{id}
Retrieve a user.
PATCH/v1/users/{id}
Update a user.
DELETE/v1/users/{id}
Delete a user.

Numbers

GET/v1/numbers
List phone numbers on your account.
POST/v1/numbers/search
Search the catalogue for available numbers to provision.
NameTypeDescription
countryrequiredstringISO 3166-1 alpha-2 country code.
typeenumOne of local, mobile, tollfree.
prefixstringOptional area-code prefix, e.g. 40264 for Cluj.
featuresstring[]Any of voice, sms, fax.
POST/v1/numbers/{id}/attach
Attach a provisioned number to a call flow.

Call flows

GET/v1/call_flows
List call flows.
POST/v1/call_flows
Create a new call flow from a JSON definition.
json
{
  "name": "Main IVR",
  "entry": "greeting_1",
  "blocks": {
    "greeting_1": {
      "type": "greeting",
      "audio_url": "https://assets.acme.io/welcome.mp3",
      "next": "ivr_1"
    },
    "ivr_1": {
      "type": "ivr",
      "prompt_tts": "Press 1 for sales, 2 for support.",
      "choices": {
        "1": "queue_sales",
        "2": "queue_support"
      },
      "timeout_next": "voicemail_1"
    },
    "queue_sales": { "type": "queue", "queue_id": "que_01HZY..." },
    "queue_support": { "type": "queue", "queue_id": "que_01HZZ..." },
    "voicemail_1": { "type": "voicemail", "mailbox_id": "vm_01HZA..." }
  }
}
GET/v1/call_flows/{id}
Retrieve a call flow.
PATCH/v1/call_flows/{id}
Update a call flow.
DELETE/v1/call_flows/{id}
Delete a call flow.

Calls

POST/v1/calls
Place an outbound call (click-to-call).
NameTypeDescription
fromrequiredstringE.164 caller-ID number that your account owns.
torequiredstringE.164 destination number.
user_idstringUser whose device should ring first (leg A).
recordingbooleanRecord this call. Defaults to the flow's setting.
ai_featuresstring[]Subset of summary, sentiment, transcript, topics.
bash
curl -X POST https://api.odygy.com/v1/calls \
  -H "Authorization: Bearer $ODYGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+40264000111",
    "to": "+441632960000",
    "user_id": "usr_01HZY...",
    "recording": true,
    "ai_features": ["summary", "sentiment"]
  }'
GET/v1/calls
List historical calls (CDRs).
GET/v1/calls/{id}
Retrieve a single CDR.

Recordings

GET/v1/calls/{id}/recording
Download the recording for a call. The response is a short-lived, signed URL pointing to an MP3 file.
GET/v1/calls/{id}/transcript
Return a time-coded transcript with speaker labels. Available once the AI pipeline has finished processing (typically within a minute of call hangup).
json
{
  "call_id": "cal_01HZY...",
  "language": "en-US",
  "segments": [
    { "t": 0.00, "speaker": "caller", "text": "Hi, I'm calling about invoice 4011." },
    { "t": 3.42, "speaker": "agent",  "text": "Of course — let me pull it up." }
  ]
}

Webhooks

Subscribe to events to receive real-time notifications. Register an endpoint once, then listen for any number of event types.

POST/v1/webhooks
Create a webhook endpoint.
NameTypeDescription
urlrequiredstringHTTPS URL that will receive POSTs.
eventsrequiredstring[]Event types to subscribe to.
secretstringUsed to sign payloads. If omitted, one is generated.

Event types

  • call.initiated — a call has started.
  • call.answered — an agent picked up.
  • call.completed — hangup, with CDR.
  • recording.ready — recording file is available.
  • transcript.ready — AI transcript is available.
  • voicemail.received — new voicemail dropped.
  • number.ported — port-in completed.

Signature verification

Every request carries an Odygy-Signature header containing a timestamp and an HMAC-SHA256 of the raw body using your endpoint secret. Reject any request whose signature is invalid or whose timestamp is more than 5 minutes old.

javascript
import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const [tPart, sigPart] = header.split(",");
  const t = tPart.split("=")[1];
  const given = sigPart.split("=")[1];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(given, "hex"),
    Buffer.from(expected, "hex")
  );
}

SDKs

Official, type-safe SDKs wrap the REST API:

  • Node.js / TypeScript npm install @odygy/phone-systems
  • Pythonpip install odygy
  • Go go get github.com/odygy/phone-systems-go
typescript
import { Odygy } from "@odygy/phone-systems";

const odygy = new Odygy({ apiKey: process.env.ODYGY_API_KEY });

const call = await odygy.calls.create({
  from: "+40264000111",
  to: "+441632960000",
  recording: true,
  aiFeatures: ["summary", "sentiment"],
});

console.log(call.id);

Changelog

  • 2026-04-01 — v1 general availability. ai_features now accepts topics.
  • 2026-02-14 — Webhooks signature format updated. Old format is still accepted until 2026-06-01.
  • 2026-01-08 — New /v1/calls/{id}/transcript endpoint.
Backwards-incompatible changes are announced at least 90 days in advance and are tied to a new API version (/v2, etc.). v1 endpoints remain supported for at least 12 months after deprecation.