OdyOdy Help

API overview and authentication

Updated Mon Aug 17 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

The Ody REST API lives at https://api.ody.co/v1/api and authenticates every request with an API key sent as a Bearer token — create one in Settings → Developer API, then call the endpoints below.

Authentication

Send your key in the Authorization header on every request:

curl https://api.ody.co/v1/api/contacts \
  -H "Authorization: Bearer ody_live_…"
  • Keys come in two modes: ody_live_… keys are live, ody_test_… keys behave identically but message sends are simulated (recorded with deliveryStatus: "simulated", never transmitted). See Build safely with test mode.
  • A missing, invalid, or revoked key returns 401 unauthenticated.
  • A key narrowed by scopes returns 403 permission_denied — naming the missing scope — when it calls an endpoint outside its grants.
  • Every call runs with the permissions of the member who created the key, inside that workspace only.

Endpoints

Method & path What it does Required scope
GET /v1/api/contacts List or search contacts (?q=, paginated) contacts:read
POST /v1/api/contacts Create a contact contacts:write
GET /v1/api/contacts/:id Fetch a contact's full detail contacts:read
PATCH /v1/api/contacts/:id Update a contact (only provided fields change) contacts:write
DELETE /v1/api/contacts/:id Delete a contact contacts:write
GET /v1/api/conversations List conversations (?status=open|done, paginated) conversations:read
GET /v1/api/conversations/:id Fetch a full thread conversations:read
PATCH /v1/api/conversations/:id Set status — body {"status":"open"} or {"status":"done"} conversations:write
POST /v1/api/messages Send an SMS/MMS (supports Idempotency-Key) messages:send
GET /v1/api/messages/search Search message bodies and transcripts (?q=) conversations:read
GET /v1/api/calls List call & voicemail history conversations:read
POST /v1/api/calls Place an outbound AI call — your published agent dials calls:place
GET /v1/api/numbers List your workspace's numbers numbers:read
GET /v1/api/numbers/available Search numbers you can buy (?country=, ?startsWith=, ?contains=, ?locality=, ?state=, ?limit=) numbers:manage
POST /v1/api/numbers Buy a number — billed like an in-app purchase (supports Idempotency-Key) numbers:manage
POST /v1/api/numbers/:e164/connect and …/disconnect Route a number to or away from your AI agent agent:manage
GET /v1/api/messaging 10DLC registration status (step, canText) numbers:manage
POST /v1/api/messaging/registration Submit your 10DLC business + campaign (billed) numbers:manage
POST /v1/api/messaging/refresh Re-check and advance 10DLC carrier review numbers:manage
GET/PATCH /v1/api/agent Read or update your AI agent's config (draft) agent:manage
POST /v1/api/agent/publish Push your agent config live agent:manage
GET /v1/api/webhook-events List subscribable webhook event types webhooks:manage
GET/POST /v1/api/webhooks and /v1/api/webhooks/:id… Manage outbound webhook endpoints webhooks:manage
GET /v1/api/openapi.json The OpenAPI 3.1 spec none

List endpoints paginate with ?limit= and ?cursor= — see Pagination, idempotency, and error handling.

Contacts

POST /v1/api/contacts needs at least one identity field: firstName, lastName, company, a phone, or an email. Phones and emails accept a simple string ("phone": "+15125550123") or full labeled arrays ("phones": [{"value": "+15125550123", "label": "work"}]). On PATCH, only the fields you provide change; phones, emails, notes, properties, and tags are replaced wholesale when supplied.

Sending a message

POST /v1/api/messages accepts either an existing conversationId or a recipient phone number in to (E.164 format). With to, Ody picks your workspace's sending number (or honors a from you own), creates the conversation if needed, and threads the message. Add up to 10 publicly fetchable mediaUrls for MMS:

curl https://api.ody.co/v1/api/messages \
  -H "Authorization: Bearer ody_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{"to":"+15125550123","body":"Your order shipped!"}'

A workspace with no active phone number gets 409 failed_precondition. Retries with the same Idempotency-Key within 24 hours replay the first response instead of double-sending.

Numbers, texting, AI calls, and your agent

The API can also run a phone line end to end: search and buy numbers, register 10DLC texting, configure and publish your AI agent, route numbers to it, and place outbound AI calls. These endpoints use three scopes of their own — numbers:manage (Buy numbers & 10DLC), calls:place (Place AI calls), and agent:manage (Manage AI agent) — and everything they do lands in your account exactly as if you did it in the app, with the same billing and rules. See Buy numbers and register texting via the API and Place AI calls and manage Astra via the API.

Errors, request IDs, and rate limits

Every error uses a uniform envelope, and every response (success or error) carries an X-Request-Id header — quote it when you contact support:

{ "error": { "code": "not_found", "message": "Contact not found", "requestId": "req_…" } }

Every key gets 600 requests/minute with standard RateLimit-* headers. Details in API rate limits and best practices and Pagination, idempotency, and error handling.

Related articles

Frequently asked questions

What can the API do today?

Full contact CRUD, list and update conversations, send SMS/MMS, search messages, list call history, search and buy numbers, register 10DLC texting, place outbound AI calls, configure and publish your AI agent, and manage outbound webhooks.

Can an API key access other workspaces?

No. A key is bound to the workspace it was created in and acts as the member who created it — the same permission rules as the app apply.

Is there a machine-readable spec?

Yes — an OpenAPI 3.1 document at https://api.ody.co/v1/api/openapi.json, no authentication required.

More in Developer API