openapi: 3.0.3 info: title: 'Okta WhatsApp API Documentation' description: '' version: 1.0.0 servers: - url: 'https://whatsapp.getokta.io' tags: - name: Channels description: "\nRead-only listing of the WhatsApp channels connected to the active\norganization. Provisioning happens in the admin UI; the API only\nsurfaces the connected channels so callers can pick one for outbound\nsends." - name: Contacts description: "\nManage WhatsApp contacts inside the authenticated organization." - name: Conversations description: '' - name: 'Embed widget' description: "\nServer-side API for the embed SDK. The host app (e.g. a Laravel back-end)\ncalls this to mint a short-lived signed token for the current visitor,\nthen hands the token to the browser, which passes it to OktaWa.boot({ token }).\n\nThe token carries the visitor's external reference + optional context\nclaims, so the embedded widget knows who it's talking to without ever\nexposing the org-side signing secret to the browser." - name: Endpoints description: '' - name: Messages description: "\nPublic API surface for sending messages from external apps.\n\nThe integrating app provides a destination wa_id (or the ulid of an\nexisting conversation) and the message body. We resolve / create the\nContact + Conversation as needed, then hand off to SendMessageAction\nwhich queues the actual provider call." - name: OAuth description: "\nExchanges a one-time authorization code from the Connect screen\n(/connect → user authorizes) for a Sanctum personal access token the\nexternal app can then use as Authorization: Bearer ...\n\nThe flow is intentionally simpler than full OAuth2: no client_secret,\nno PKCE. Security relies on:\n - Codes are one-time (used_at flag).\n - Codes expire in 5 minutes.\n - Codes are bound to a redirect_uri at issue time and the host must\n send the same redirect_uri at exchange time, so an attacker who\n intercepts the redirect URL still can't redeem the code without\n also impersonating the registered redirect target." - name: Templates description: '' components: securitySchemes: default: type: http scheme: bearer description: 'Issue a token from /app/integrations/api-tokens and pass it as Authorization: Bearer .... Tokens carry abilities (read / write / send / admin) — endpoints reject calls that lack the required ability.' security: - default: [] paths: /api/v1/channels: get: summary: 'List channels' operationId: listChannels description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 type: cloud status: connected display_name: 'Sales line' phone_number: '+966500000000' connected_at: '2026-05-01T08:00:00+00:00' last_seen_at: '2026-05-06T10:21:00+00:00' properties: data: type: array example: - id: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 type: cloud status: connected display_name: 'Sales line' phone_number: '+966500000000' connected_at: '2026-05-01T08:00:00+00:00' last_seen_at: '2026-05-06T10:21:00+00:00' items: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 type: type: string example: cloud status: type: string example: connected display_name: type: string example: 'Sales line' phone_number: type: string example: '+966500000000' connected_at: type: string example: '2026-05-01T08:00:00+00:00' last_seen_at: type: string example: '2026-05-06T10:21:00+00:00' tags: - Channels '/api/v1/channels/{id}': get: summary: 'Get a channel' operationId: getAChannel description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Channels parameters: - in: path name: id description: 'Channel ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 required: true schema: type: string /api/v1/contacts: get: summary: 'List contacts' operationId: listContacts description: "Returns a paginated list of contacts in the active organization,\nordered by most recently created first." parameters: - in: query name: search description: 'Filter by name, phone, or wa_id (substring).' example: ali required: false schema: type: string description: 'Filter by name, phone, or wa_id (substring).' example: ali - in: query name: per_page description: 'Page size, between 1 and 100. Defaults to 25.' example: 50 required: false schema: type: integer description: 'Page size, between 1 and 100. Defaults to 25.' example: 50 - in: query name: page description: 'Page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Page number. Defaults to 1.' example: 1 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 wa_id: '966500000000' phone: '+966500000000' name: Ali email: null language: ar last_seen_at: '2026-05-06T10:21:00+00:00' created_at: '2026-05-01T08:00:00+00:00' meta: current_page: 1 per_page: 25 total: 1 properties: data: type: array example: - id: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 wa_id: '966500000000' phone: '+966500000000' name: Ali email: null language: ar last_seen_at: '2026-05-06T10:21:00+00:00' created_at: '2026-05-01T08:00:00+00:00' items: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 wa_id: type: string example: '966500000000' phone: type: string example: '+966500000000' name: type: string example: Ali email: type: string example: null nullable: true language: type: string example: ar last_seen_at: type: string example: '2026-05-06T10:21:00+00:00' created_at: type: string example: '2026-05-01T08:00:00+00:00' meta: type: object properties: current_page: type: integer example: 1 per_page: type: integer example: 25 total: type: integer example: 1 tags: - Contacts post: summary: 'Create or update a contact' operationId: createOrUpdateAContact description: "Idempotent on (organization, wa_id). Existing contact attributes are\noverwritten by the values you send — omit a field to leave it untouched." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 wa_id: '966500000000' phone: '+966500000000' name: Ali email: null language: ar properties: data: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 wa_id: type: string example: '966500000000' phone: type: string example: '+966500000000' name: type: string example: Ali email: type: string example: null nullable: true language: type: string example: ar tags: - Contacts requestBody: required: true content: application/json: schema: type: object properties: wa_id: type: string description: 'E.164 number without the leading +.' example: '966500000000' phone: type: string description: 'Display phone (with +).' example: '+966500000000' nullable: true name: type: string description: 'Display name.' example: Ali nullable: true email: type: string description: 'Optional email.' example: ali@example.com nullable: true language: type: string description: 'Two-letter language hint.' example: ar nullable: true profile: type: object description: 'Free-form profile bag (max 4 KB).' example: [] properties: { } nullable: true custom_fields: type: object description: 'Free-form custom fields bag.' example: [] properties: { } nullable: true required: - wa_id '/api/v1/contacts/{id}': get: summary: 'Get a contact' operationId: getAContact description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. 404: description: '' content: application/json: schema: type: object example: message: 'No query results for model [Contact].' properties: message: type: string example: 'No query results for model [Contact].' tags: - Contacts parameters: - in: path name: id description: 'The contact ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 required: true schema: type: string /api/v1/conversations: get: summary: 'List conversations' operationId: listConversations description: "Sorted by `last_message_at` descending so the freshest activity\nappears first." parameters: - in: query name: status description: 'Filter by status (open, pending, snoozed, closed).' example: open required: false schema: type: string description: 'Filter by status (open, pending, snoozed, closed).' example: open - in: query name: channel_id description: 'Filter by channel ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 required: false schema: type: string description: 'Filter by channel ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 - in: query name: per_page description: '1-100, defaults to 25.' example: 25 required: false schema: type: integer description: '1-100, defaults to 25.' example: 25 responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 status: open priority: normal unread_count: 2 channel_id: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 contact_id: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 last_message_at: '2026-05-06T10:21:00+00:00' properties: data: type: array example: - id: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 status: open priority: normal unread_count: 2 channel_id: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 contact_id: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 last_message_at: '2026-05-06T10:21:00+00:00' items: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 status: type: string example: open priority: type: string example: normal unread_count: type: integer example: 2 channel_id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 contact_id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1 last_message_at: type: string example: '2026-05-06T10:21:00+00:00' tags: - Conversations '/api/v1/conversations/{id}': get: summary: 'Get a conversation' operationId: getAConversation description: '' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Conversations parameters: - in: path name: id description: 'Conversation ULID.' example: architecto required: true schema: type: string '/api/v1/conversations/{id}/messages': get: summary: 'List messages in a conversation' operationId: listMessagesInAConversation description: 'Newest first.' parameters: - in: query name: per_page description: '1-100, defaults to 50.' example: 50 required: false schema: type: integer description: '1-100, defaults to 50.' example: 50 responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Conversations parameters: - in: path name: id description: 'Conversation ULID.' example: architecto required: true schema: type: string /api/v1/embed-tokens: post: summary: 'Mint a signed widget token' operationId: mintASignedWidgetToken description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: token: eyJpc3MiOi... expires_in: 900 slug: marketing-site properties: data: type: object properties: token: type: string example: eyJpc3MiOi... expires_in: type: integer example: 900 slug: type: string example: marketing-site tags: - 'Embed widget' requestBody: required: true content: application/json: schema: type: object properties: slug: type: string description: 'The widget config slug.' example: marketing-site subject: type: string description: 'Visitor reference (your own user id, session id, etc).' example: user-12345 context: type: object description: 'Free-form context bag (max 4 KB serialized).' example: [] properties: { } nullable: true ttl_seconds: type: integer description: 'Token lifetime, 60-86400. Defaults to 900.' example: 900 nullable: true required: - slug - subject /api/integrations/meta/embedded-signup: post: summary: '' operationId: postApiIntegrationsMetaEmbeddedSignup description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'Must not be greater than 1024 characters.' example: b waba_id: type: string description: 'Must not be greater than 64 characters.' example: 'n' required: - code - waba_id /api/webhooks/neoleap: post: summary: '' operationId: postApiWebhooksNeoleap description: '' parameters: [] responses: { } tags: - Endpoints /api/v1/messages: post: summary: 'Send a message' operationId: sendAMessage description: "Send a free-form text or media message to a wa_id (auto-creates the\ncontact + conversation if needed) or into an existing conversation\nvia `conversation_id`. The message is queued; the response carries\nthe queued message resource — poll `GET /conversations/{id}/messages`\nor subscribe to a webhook to track delivery status." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01HZK7P5R3Q6V0YH4XJ3M8MSG1 conversation_id: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 channel_id: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 direction: outbound type: text body: 'Your order is on the way!' status: queued created_at: '2026-05-06T10:21:00+00:00' properties: data: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8MSG1 conversation_id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 channel_id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 direction: type: string example: outbound type: type: string example: text body: type: string example: 'Your order is on the way!' status: type: string example: queued created_at: type: string example: '2026-05-06T10:21:00+00:00' 404: description: '' content: application/json: schema: type: object example: message: 'Channel not found.' properties: message: type: string example: 'Channel not found.' 422: description: '' content: application/json: schema: type: object example: message: 'The channel id field is required when conversation id is not present.' errors: channel_id: - ... properties: message: type: string example: 'The channel id field is required when conversation id is not present.' errors: type: object properties: channel_id: type: array example: - ... items: type: string tags: - Messages requestBody: required: true content: application/json: schema: type: object properties: channel_id: type: string description: 'The channel ULID. Required when sending by `wa_id`.' example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 conversation_id: type: string description: 'Reuse an existing conversation by ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8CONV1 nullable: true wa_id: type: string description: 'Destination wa_id (E.164 without +). Required unless `conversation_id` is set.' example: '966500000000' type: type: string description: 'The message type: text|image|document|audio|video. Defaults to "text".' example: text nullable: true body: type: string description: 'The message text or media caption.' example: 'Your order is on the way!' media_url: type: string description: 'For image/document/audio/video — a publicly fetchable HTTPS URL.' example: 'https://cdn.example.com/file.pdf' nullable: true required: - body /api/v1/oauth/token: post: summary: 'Exchange code for token' operationId: exchangeCodeForToken description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: access_token: 1|abc... token_type: Bearer abilities: - read - send properties: data: type: object properties: access_token: type: string example: 1|abc... token_type: type: string example: Bearer abilities: type: array example: - read - send items: type: string 400: description: '' content: application/json: schema: type: object example: error: invalid_grant message: 'Authorization code is invalid, used, or expired.' properties: error: type: string example: invalid_grant message: type: string example: 'Authorization code is invalid, used, or expired.' tags: - OAuth requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'The one-time code from the /connect redirect.' example: 4f9e2c... redirect_uri: type: string description: 'Must match the redirect_uri the user authorized on the consent screen.' example: 'https://crm.example.com/oktawa/callback' required: - code - redirect_uri /api/v1/templates: get: summary: 'List templates' operationId: listTemplates description: '' parameters: - in: query name: status description: 'Filter by Meta status (DRAFT, PENDING, APPROVED, REJECTED, PAUSED, DISABLED).' example: APPROVED required: false schema: type: string description: 'Filter by Meta status (DRAFT, PENDING, APPROVED, REJECTED, PAUSED, DISABLED).' example: APPROVED - in: query name: language description: 'Filter by language code.' example: ar required: false schema: type: string description: 'Filter by language code.' example: ar responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - Templates /api/v1/templates/send: post: summary: 'Send a template message' operationId: sendATemplateMessage description: "Send a Meta-approved template to a wa_id with optional positional\nvariables (1-indexed in Meta, 0-indexed in the array — variable {{1}}\n= $variables[0])." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01HZK7P5R3Q6V0YH4XJ3M8MSG2 type: template status: queued properties: data: type: object properties: id: type: string example: 01HZK7P5R3Q6V0YH4XJ3M8MSG2 type: type: string example: template status: type: string example: queued 404: description: '' content: application/json: schema: type: object example: message: 'Template not found.' properties: message: type: string example: 'Template not found.' tags: - Templates requestBody: required: true content: application/json: schema: type: object properties: channel_id: type: string description: 'Channel ULID.' example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1 wa_id: type: string description: 'Destination wa_id.' example: '966500000000' template_name: type: string description: 'Template name as registered with Meta.' example: order_ready language: type: string description: "Template language code. Defaults to the template's primary language." example: ar nullable: true variables: type: array description: 'Positional variable values for the body.' example: - '12345' - '120 SAR' items: type: string nullable: true required: - channel_id - wa_id - template_name