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