oToK REST API Reference
The oToK REST API (/v1) gives programmatic access to your workspace: contacts (including per-channel consent), tags and groups, WhatsApp campaigns and templates, deals and pipelines, the product catalog, payments, orders, the sales ledger, transactional email, broadcast email campaigns, newsletters, email suppressions, outbound webhooks, bookings, and customer tickets.
- Base URL:
https://app.otok.io/api/v1/ - Auth:
Authorization: Bearer otok_live_…API keys (created in Settings → Developers) - Plan: requires a plan with API access (Growth or higher); deals/pipelines, payments (incl. contact documents), payment requests, orders, sales, campaigns, email campaigns + suppressions (
email_marketing), newsletters, bookings/meeting-types, and customer tickets (customer_tickets) additionally require the matching plan feature — see feature-gated resource groups - Interactive docs: Swagger UI at
https://app.otok.io/api/v1/docs
Start with Getting Started — authentication, error envelopes, rate limits, and list/filter conventions shared by all endpoints.
Guides
| Page | Covers |
|---|---|
| Getting Started | API keys, auth, base URL, errors, rate limits, pagination & filtering |
| Contacts | Contact upsert & update, merge-conflict contract, notes |
| Tags & Contact Groups | Tag / group CRUD and membership management |
| Campaigns | WhatsApp campaigns: create, schedule, execute |
| Templates | WhatsApp templates: read + send template messages |
| Deals & Pipelines | Pipelines, deal CRUD, stage moves, win/lose, idempotent upsert |
| Products | The product catalog shared by deals and payments: idempotent upsert via external_id, deactivation instead of delete |
| Product cycles | Cohorts, runs and versions: upsert, scheduling, capacity and archiving |
| Shared reports | List and run shared saved reports |
| Events | Events and registrations: idempotent event upsert, filing under a saved event, register by contact id or inline identity, Zoom push, roster moves |
| Payments | One-time / recurring / installment payments, entries, refunds (through the gateway or recorded), VAT, metadata, funding a sale |
| Payment Requests | Hosted pay-links through the workspace's own provider: mint (idempotent with a key), list, cancel, send by email/WhatsApp/SMS, issue the tax document, refund |
| Orders | E-commerce orders: line items, refunds, mark-paid/cancel, idempotent upsert |
| Sales | The sales ledger: record sales (idempotent via external_reference), cancel/reinstate items, refunds, allocating existing charges |
| Transactional Emails | POST /v1/emails: idempotent raw sends, tracking opt-in |
| Email Campaigns | Broadcast email campaigns: the shared content contract (markdown / blocks / design_json), compile feedback, estimate, send/schedule lifecycle, idempotent upsert |
| Newsletters | Smart newsletters and their sequenced issues: idempotent issue upsert, publish-time numbering, publish/schedule lifecycle |
| Consent & Suppressions | Per-channel consent read/write on contacts + the email suppression list — two independent layers that compose at send time |
| Webhooks | Twelve event families — email, order, payment-request, payment, contact, message, deal, sale, booking, attendance, form, ticket: registration, signatures, retries |
| Bookings & Meeting Types | Availability slots, booking lifecycle, host reassignment |
| Customer Tickets | Open, read, answer and triage customer tickets: idempotent open via external_reference, idempotent replies, conversation paging, customer-email outcomes |
Endpoint summary
| Resource | Endpoints |
|---|---|
| Contacts | GET /v1/contacts · GET /v1/contacts/:id · POST /v1/contacts (upsert) · PATCH /v1/contacts/:id · GET /v1/contacts/:id/documents · GET /v1/contacts/:id/payment-methods · POST /v1/contacts/:id/charges |
| Contact consent | GET /v1/contacts/:id/consent · PUT /v1/contacts/:id/consent/:channel |
| Contact notes | GET /v1/contacts/:id/notes · POST /v1/contacts/:id/notes · PATCH /v1/notes/:id · DELETE /v1/notes/:id |
| Tags | GET /v1/tags · GET /v1/tags/:id · POST /v1/tags · PATCH /v1/tags/:id |
| Contact groups | GET /v1/contact-groups · GET /v1/contact-groups/:id · POST /v1/contact-groups · PATCH /v1/contact-groups/:id |
| Campaigns (WhatsApp) | GET /v1/campaigns · GET /v1/campaigns/:id · POST /v1/campaigns · PATCH /v1/campaigns/:id · POST /v1/campaigns/:id/execute |
| Templates (WhatsApp) | GET /v1/templates · GET /v1/templates/:id · POST /v1/templates/:id/send |
| Pipelines | GET /v1/pipelines |
| Deals | GET /v1/deals · GET /v1/deals/:id · POST /v1/deals (upsert) · PATCH /v1/deals/:id · POST /v1/deals/:id/stage · POST /v1/deals/:id/status |
| Products | GET /v1/products · GET /v1/products/:id · POST /v1/products (upsert) · PATCH /v1/products/:id |
| Payments | GET /v1/payments · GET /v1/payments/:id · POST /v1/payments (upsert) · PATCH /v1/payments/:id · POST /v1/payments/:id/cancel · POST /v1/payments/:id/entries/:entryId/mark · POST /v1/payments/:id/refund |
| Payment requests | GET /v1/payment-requests · GET /v1/payment-requests/:id · POST /v1/payment-requests (idempotent with idempotency_key) · POST /v1/payment-requests/:id/cancel · POST /v1/payment-requests/:id/send-link · POST /v1/payment-requests/:id/document · POST /v1/payment-requests/:id/refund |
| Orders | GET /v1/orders · GET /v1/orders/:id · POST /v1/orders (upsert) · POST /v1/orders/:id/refunds · POST /v1/orders/:id/mark-paid · POST /v1/orders/:id/cancel |
| Sales | GET /v1/sales · GET /v1/sales/:id · POST /v1/sales (idempotent) · PATCH /v1/sales/:id · DELETE /v1/sales/:id · POST /v1/sales/:id/cancel · POST /v1/sales/:id/reinstate · POST /v1/sales/:id/refund · PUT /v1/sales/:id/owner · PUT /v1/sales/:id/deal · POST /v1/sales/:id/allocations · DELETE /v1/sales/:id/allocations/:allocationId |
| Product cycles | GET/POST /v1/products/:productId/cycles · GET/PATCH /v1/product-cycles/:id |
| Reports | GET /v1/reports · POST /v1/reports/:id/run |
| Events | GET /v1/events · GET /v1/events/:id · POST /v1/events (upsert) · GET/POST /v1/events/:id/attendances · PATCH /v1/attendances/:id |
| Emails | POST /v1/emails |
| Email campaigns | GET /v1/email-campaigns · GET /v1/email-campaigns/:id · POST /v1/email-campaigns (upsert) · PATCH /v1/email-campaigns/:id · GET /v1/email-campaigns/:id/estimate · POST /v1/email-campaigns/:id/send · POST /v1/email-campaigns/:id/schedule · POST /v1/email-campaigns/:id/unschedule |
| Newsletters | GET /v1/newsletters · GET /v1/newsletters/:id · POST /v1/newsletters · GET /v1/newsletters/:id/issues · POST /v1/newsletters/:id/issues (upsert) |
| Newsletter issues | GET /v1/newsletter-issues/:id · PATCH /v1/newsletter-issues/:id · DELETE /v1/newsletter-issues/:id · POST /v1/newsletter-issues/:id/publish · POST /v1/newsletter-issues/:id/schedule · POST /v1/newsletter-issues/:id/unschedule |
| Suppressions | GET /v1/suppressions · POST /v1/suppressions (idempotent add) · DELETE /v1/suppressions/:id |
| Webhook endpoints | GET /v1/webhook-endpoints · POST /v1/webhook-endpoints · DELETE /v1/webhook-endpoints/:id |
| Meeting types | GET /v1/meeting-types · GET /v1/meeting-types/:id · GET /v1/meeting-types/:id/slots · GET /v1/meeting-types/:id/embed |
| Bookings | GET /v1/bookings · GET /v1/bookings/:id · POST /v1/bookings · POST /v1/bookings/:id/cancel · POST /v1/bookings/:id/reschedule · POST /v1/bookings/:id/reassign |
| Tickets | GET /v1/tickets · GET /v1/tickets/:id · POST /v1/tickets (idempotent via external_reference) · POST /v1/tickets/:id/replies · PATCH /v1/tickets/:id |
Conventions at a glance
- Success codes:
GET/PUT/PATCH→ 200;POST→ 201 (including action routes);POST /v1/campaigns/:id/executeand the email-campaign/newsletter-issue lifecycle routes (send/schedule/unschedule/publish) → 200;DELETE /v1/webhook-endpoints/:idandDELETE /v1/suppressions/:id→ 204;DELETE /v1/newsletter-issues/:idandDELETE /v1/sales/:id→ 200 with{"success": true};DELETE /v1/sales/:id/allocations/:allocationId→ 200 with{ released, twins }.POST /v1/emails,POST /v1/ticketsandPOST /v1/tickets/:id/repliesreturn 200 on an idempotent replay. - Errors: two body shapes — a structured
{"error": {"code", "message"}}envelope on the email/email-campaign/newsletter/webhook/consent/suppression/product/ticket APIs (and the campaign execute route), and the standard{"statusCode", "message", "error"}shape elsewhere, sometimes extended with anerror_codefield. See error responses. - Pagination:
{ "data", "total", "limit", "offset" }— default limit 50 (cap 500) on most lists; deals, payments, sales, payment requests, orders, and the email-campaign/newsletter lists use default 25 (cap 100). All of those except orders reject malformedlimit/offsetwith 400; orders silently defaults/clamps them instead. The email-campaign and newsletter-issue lists reject an unknownstatusvalue with 400. - Rate limits: 100 requests/min per key (300/min for
POST /v1/emails); HTTP 429 withRetry-Afteron excess. - Idempotency: contacts upsert by phone/email/national ID; deals, payments, orders, email campaigns, and newsletter issues upsert by
external_reference(a campaign/issue replay updates content/fields while the record is still editable and never touchesstatus,scheduled_at, or an issue's number); products upsert byexternal_id; cycles by case-insensitive name within a product; order refunds byexternal_refund_id; sales byexternal_reference(a replay writes nothing) and sale refunds byidempotency_key; suppression adds are idempotent per address; emails and saved-card charges require an explicitidempotency_key; payment requests and payment refunds accept an optional one; booking creation is idempotent per slot+contact; a ticket open with a knownexternal_referencereplays the original ticket and changes nothing, and ticket replies are idempotent peridempotency_key. Idempotent create responses carry a top-level booleanduplicate(falseon a fresh create,trueon an upsert/replay) — exceptPOST /v1/orders, which returns the same full-order body for both outcomes with noduplicatefield (see Orders). APOST /v1/payment-requestswithoutidempotency_keyis not idempotent — a repeat POST mints a second payable link (see Payment Requests). - Deletion: the API never deletes customer data — contacts, deals, products, payments, orders, campaigns, tags, and contact groups have no DELETE routes (archive a product with
manual_status: "archived"). Only API-owned resources can be deleted: notes (DELETE /v1/notes/:id), webhook endpoints (DELETE /v1/webhook-endpoints/:id), suppressions (DELETE /v1/suppressions/:id— lifts a send-time block; deletes no contact data, resubscribes no one), and never-published newsletter issues (DELETE /v1/newsletter-issues/:id— published issues are never deletable).DELETE /v1/sales/:id(an unfunded, unlinked sale) andDELETE /v1/sales/:id/allocations/:allocationIdadditionally need a key with the owner-granted Allow permanent deletion through the API capability (see Sales).