MENU navbar-image

Introduction

This documentation aims to provide all the information you need to work with our API.

<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

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.

Analytics

Read the organization's aggregated daily metrics (conversations, messages, agents and social insights) as totals per metric key.

Metrics summary

requires authentication

Returns totals per metric key over a date range (inclusive). Defaults to the last 30 days when from/to are omitted. Pass platform to scope to a single social platform.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/metrics?from=2026-06-01&to=2026-06-30&platform=telegram" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-08-18T12:48:29\",
    \"to\": \"2026-08-18T12:48:29\",
    \"platform\": \"b\"
}"
const url = new URL(
    "http://localhost/api/v1/analytics/metrics"
);

const params = {
    "from": "2026-06-01",
    "to": "2026-06-30",
    "platform": "telegram",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-08-18T12:48:29",
    "to": "2026-08-18T12:48:29",
    "platform": "b"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "from": "2026-06-01",
        "to": "2026-06-30",
        "platform": null,
        "metrics": {
            "messages.inbound": 1200,
            "messages.outbound": 940,
            "conversations.opened": 310,
            "social.followers": 4820
        }
    }
}
 

Example response (422):


{
    "message": "The from field must be a valid date.",
    "errors": {
        "from": [
            "The from field must be a valid date."
        ]
    }
}
 

Request      

GET api/v1/analytics/metrics

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string  optional    

date Start date (YYYY-MM-DD). Defaults to 30 days ago. Example: 2026-06-01

to   string  optional    

date End date (YYYY-MM-DD). Defaults to today. Example: 2026-06-30

platform   string  optional    

Restrict to a platform (e.g. telegram, instagram, x). Example: telegram

Body Parameters

from   string  optional    

Must be a valid date. Example: 2026-08-18T12:48:29

to   string  optional    

Must be a valid date. Example: 2026-08-18T12:48:29

platform   string  optional    

Must not be greater than 64 characters. Example: b

Campaigns

Broadcast (bulk / drip) message campaigns inside the authenticated organization. Create a draft, then queue it to materialize its audience.

List campaigns

requires authentication

Returns a paginated list of campaigns, newest first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/campaigns?status=draft&per_page=25&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/campaigns"
);

const params = {
    "status": "draft",
    "per_page": "25",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "01HZK7P5R3Q6V0YH4XJ3M8CMP1",
            "name": "Ramadan promo",
            "type": "bulk",
            "status": "draft",
            "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
            "template_id": null,
            "audience_size": 0,
            "queued_count": 0,
            "sent_count": 0,
            "delivered_count": 0,
            "read_count": 0,
            "failed_count": 0,
            "scheduled_at": null,
            "started_at": null,
            "finished_at": null,
            "created_at": "2026-07-04T08:00:00+00:00"
        }
    ],
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "total": 1
    }
}
 

Request      

GET api/v1/campaigns

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status (draft, scheduled, queueing, running, completed, cancelled, failed). Example: draft

per_page   integer  optional    

Page size, 1-100. Defaults to 25. Example: 25

page   integer  optional    

Page number. Example: 1

Get a campaign

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/campaigns/01HZK7P5R3Q6V0YH4XJ3M8CMP1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/campaigns/01HZK7P5R3Q6V0YH4XJ3M8CMP1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):


{
    "message": "No query results for model [Campaign]."
}
 

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/campaigns/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

The campaign ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CMP1

Create a draft campaign

requires authentication

Creates a campaign in the draft state. The audience is not materialized until you call the queue endpoint. audience_filter accepts contact ULIDs and/or tag slugs; contact ULIDs are resolved to the recipients selected at queue time.

Example request:
curl --request POST \
    "http://localhost/api/v1/campaigns" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Ramadan promo\",
    \"channel_id\": \"01HZK7P5R3Q6V0YH4XJ3M8CHN1\",
    \"template_id\": \"01HZK7P5R3Q6V0YH4XJ3M8TPL1\",
    \"type\": \"bulk\",
    \"audience_filter\": {
        \"contact_ids\": [
            \"01HZK7P5R3Q6V0YH4XJ3M8AAA1\"
        ],
        \"tag_slugs\": [
            \"vip\"
        ]
    }
}"
const url = new URL(
    "http://localhost/api/v1/campaigns"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Ramadan promo",
    "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
    "template_id": "01HZK7P5R3Q6V0YH4XJ3M8TPL1",
    "type": "bulk",
    "audience_filter": {
        "contact_ids": [
            "01HZK7P5R3Q6V0YH4XJ3M8AAA1"
        ],
        "tag_slugs": [
            "vip"
        ]
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8CMP1",
        "name": "Ramadan promo",
        "type": "bulk",
        "status": "draft",
        "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
        "template_id": null,
        "audience_size": 0,
        "queued_count": 0,
        "created_at": "2026-07-04T08:00:00+00:00"
    }
}
 

Example response (404):


{
    "message": "No query results for model [Channel]."
}
 

Example response (422):


{
    "message": "The name field is required.",
    "errors": {
        "name": [
            "The name field is required."
        ]
    }
}
 

Request      

POST api/v1/campaigns

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Human-readable campaign name. Example: Ramadan promo

channel_id   string     

The sending channel ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

template_id   string  optional    

A message template ULID to send. Example: 01HZK7P5R3Q6V0YH4XJ3M8TPL1

type   string  optional    

Campaign type: bulk|drip. Defaults to "bulk". Example: bulk

audience_filter   object  optional    

Audience selection.

contact_ids   string[]  optional    

Contact ULIDs to include.

tag_slugs   string[]  optional    

Tag slugs to include.

Queue a campaign

requires authentication

Materializes the campaign's audience into recipients and moves it to the running state. Only draft and scheduled campaigns can be queued; other states are returned unchanged.

Example request:
curl --request POST \
    "http://localhost/api/v1/campaigns/01HZK7P5R3Q6V0YH4XJ3M8CMP1/queue" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/campaigns/01HZK7P5R3Q6V0YH4XJ3M8CMP1/queue"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8CMP1",
        "name": "Ramadan promo",
        "status": "running",
        "audience_size": 120,
        "queued_count": 120
    }
}
 

Example response (404):


{
    "message": "No query results for model [Campaign]."
}
 

Request      

POST api/v1/campaigns/{ulid}/queue

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

The campaign ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CMP1

Channels

Read-only listing of the channels connected to the active organization. Provisioning happens in the admin UI; the API only surfaces the connected channels so callers can pick one for outbound sends.

List channels

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/channels?type=whatsapp&status=connected" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/channels"
);

const params = {
    "type": "whatsapp",
    "status": "connected",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
        }
    ]
}
 

Request      

GET api/v1/channels

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by channel type (cloud_api, baileys, telegram, instagram_dm, twitter, linkedin, tiktok, email) — or the family alias whatsapp, which covers both cloud_api and baileys. Example: whatsapp

status   string  optional    

Filter by connection status (connected, disconnected, pending, failed). Example: connected

Get a channel

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/channels/01HZK7P5R3Q6V0YH4XJ3M8CHN1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/channels/01HZK7P5R3Q6V0YH4XJ3M8CHN1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/channels/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Channel ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

Delete a channel

requires authentication

Disconnects the channel (stopping any live WhatsApp gateway session), emits a channel.deleted webhook, then removes it. Use this to clean up stale channels — e.g. old WhatsApp links left "awaiting scan" — without opening the dashboard. Requires the write (or admin) ability.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/channels/01HZK7P5R3Q6V0YH4XJ3M8CHN1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/channels/01HZK7P5R3Q6V0YH4XJ3M8CHN1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "deleted": true
}
 

Request      

DELETE api/v1/channels/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Channel ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

Contacts

Manage WhatsApp contacts inside the authenticated organization.

List contacts

requires authentication

Returns a paginated list of contacts in the active organization, ordered by most recently created first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/contacts?search=ali&per_page=50&page=1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/contacts"
);

const params = {
    "search": "ali",
    "per_page": "50",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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
    }
}
 

Request      

GET api/v1/contacts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

search   string  optional    

Filter by name, phone, or wa_id (substring). Example: ali

per_page   integer  optional    

Page size, between 1 and 100. Defaults to 25. Example: 50

page   integer  optional    

Page number. Defaults to 1. Example: 1

Get a contact

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/contacts/01HZK7P5R3Q6V0YH4XJ3M8AAA1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/contacts/01HZK7P5R3Q6V0YH4XJ3M8AAA1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):


{
    "message": "No query results for model [Contact]."
}
 

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/contacts/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The contact ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1

Create or update a contact

requires authentication

Idempotent on (organization, wa_id). Existing contact attributes are overwritten by the values you send — omit a field to leave it untouched.

Example request:
curl --request POST \
    "http://localhost/api/v1/contacts" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"wa_id\": \"966500000000\",
    \"phone\": \"+966500000000\",
    \"name\": \"Ali\",
    \"email\": \"ali@example.com\",
    \"language\": \"ar\",
    \"profile\": [],
    \"custom_fields\": []
}"
const url = new URL(
    "http://localhost/api/v1/contacts"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "wa_id": "966500000000",
    "phone": "+966500000000",
    "name": "Ali",
    "email": "ali@example.com",
    "language": "ar",
    "profile": [],
    "custom_fields": []
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8AAA1",
        "wa_id": "966500000000",
        "phone": "+966500000000",
        "name": "Ali",
        "email": null,
        "language": "ar"
    }
}
 

Request      

POST api/v1/contacts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

wa_id   string     

E.164 number without the leading +. Example: 966500000000

phone   string  optional    

Display phone (with +). Example: +966500000000

name   string  optional    

Display name. Example: Ali

email   string  optional    

Optional email. Example: ali@example.com

language   string  optional    

Two-letter language hint. Example: ar

profile   object  optional    

Free-form profile bag (max 4 KB).

custom_fields   object  optional    

Free-form custom fields bag.

Conversations

List conversations

requires authentication

Sorted by last_message_at descending so the freshest activity appears first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/conversations?status=open&channel_id=01HZK7P5R3Q6V0YH4XJ3M8CHN1&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/conversations"
);

const params = {
    "status": "open",
    "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "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"
        }
    ]
}
 

Request      

GET api/v1/conversations

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status (open, pending, snoozed, closed). Example: open

channel_id   string  optional    

Filter by channel ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

per_page   integer  optional    

1-100, defaults to 25. Example: 25

Get a conversation

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/conversations/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/conversations/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/conversations/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Conversation ULID. Example: architecto

List messages in a conversation

requires authentication

Newest first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/conversations/architecto/messages?per_page=50" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/conversations/architecto/messages"
);

const params = {
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/conversations/{id}/messages

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Conversation ULID. Example: architecto

Query Parameters

per_page   integer  optional    

1-100, defaults to 50. Example: 50

Email broadcasts

Bulk email — compose a broadcast to an audience of CRM contacts, then queue it. The audience is every contact in your organisation that has an email address and hasn't opted out; an optional tag filter narrows it. Each recipient is sent through the same pipeline as transactional email, so the suppression list is always honoured.

List email broadcasts

requires authentication

Returns the org's broadcasts, newest first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails/broadcasts?status=draft&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/broadcasts"
);

const params = {
    "status": "draft",
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/emails/broadcasts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status: draft|scheduled|queueing|sending|sent|failed. Example: draft

per_page   integer  optional    

Page size (max 100). Example: 25

Get a broadcast

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails/broadcasts/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/broadcasts/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/emails/broadcasts/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Create a broadcast

requires authentication

Creates a draft broadcast. Queue it with POST /emails/broadcasts/{ulid}/queue.

Example request:
curl --request POST \
    "http://localhost/api/v1/emails/broadcasts" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"July newsletter\",
    \"from\": \"Acme <hello@mail.acme.com>\",
    \"subject\": \"What\'s new in July\",
    \"html\": \"<h1>Hello!<\\/h1>\",
    \"text\": \"Hello!\",
    \"audience\": {
        \"tag_slugs\": [
            \"newsletter\"
        ]
    },
    \"scheduled_at\": \"2026-07-20T09:00:00Z\"
}"
const url = new URL(
    "http://localhost/api/v1/emails/broadcasts"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "July newsletter",
    "from": "Acme <hello@mail.acme.com>",
    "subject": "What's new in July",
    "html": "<h1>Hello!<\/h1>",
    "text": "Hello!",
    "audience": {
        "tag_slugs": [
            "newsletter"
        ]
    },
    "scheduled_at": "2026-07-20T09:00:00Z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": "01H...",
        "status": "draft",
        "name": "July newsletter"
    }
}
 

Request      

POST api/v1/emails/broadcasts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

A label for the broadcast. Example: July newsletter

from   string     

Sender, Name <addr@domain> or a bare address on a verified domain. Example: Acme <hello@mail.acme.com>

subject   string  optional    

The subject line. Example: What's new in July

html   string  optional    

HTML body (at least one of html/text required). Example: <h1>Hello!</h1>

text   string  optional    

Plain-text body. Example: Hello!

audience   object  optional    

Optional audience selector.

tag_slugs   string[]  optional    

Must not be greater than 255 characters.

scheduled_at   string  optional    

Optional ISO-8601 time to send at. Example: 2026-07-20T09:00:00Z

Queue a broadcast

requires authentication

Materialises the audience and fans out one send per recipient. Returns the broadcast with its updated status and queued_count.

Example request:
curl --request POST \
    "http://localhost/api/v1/emails/broadcasts/architecto/queue" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/broadcasts/architecto/queue"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/v1/emails/broadcasts/{ulid}/queue

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Emails

Transactional email — send programmatic email (receipts, OTPs, notifications) as one of your verified sending domains, and read the send log. Inbound replies land in the unified inbox, not here.

List sent emails

requires authentication

Returns the org's transactional send log, newest first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails?status=sent&per_page=50" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails"
);

const params = {
    "status": "sent",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/emails

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status: queued|sent|delivered|bounced|complained|failed. Example: sent

per_page   integer  optional    

Page size (max 100). Example: 50

Email delivery analytics

requires authentication

Returns a status-count summary (with delivery/bounce rates) plus a per-day series over a date range (inclusive). Defaults to the last 30 days when from/to are omitted. The range is clamped to 366 days.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails/analytics?from=2026-06-01&to=2026-06-30" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-08-18T12:48:29\",
    \"to\": \"2026-08-18T12:48:29\"
}"
const url = new URL(
    "http://localhost/api/v1/emails/analytics"
);

const params = {
    "from": "2026-06-01",
    "to": "2026-06-30",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-08-18T12:48:29",
    "to": "2026-08-18T12:48:29"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "from": "2026-06-01",
        "to": "2026-06-30",
        "summary": {
            "total": 120,
            "queued": 0,
            "sent": 100,
            "delivered": 90,
            "bounced": 5,
            "complained": 1,
            "failed": 4,
            "delivery_rate": 75,
            "bounce_rate": 4.17
        },
        "series": [
            {
                "date": "2026-06-01",
                "sent": 4,
                "delivered": 3,
                "bounced": 0
            }
        ]
    }
}
 

Example response (422):


{
    "message": "The from field must be a valid date.",
    "errors": {
        "from": [
            "The from field must be a valid date."
        ]
    }
}
 

Request      

GET api/v1/emails/analytics

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string  optional    

date Start date (YYYY-MM-DD). Defaults to 30 days ago. Example: 2026-06-01

to   string  optional    

date End date (YYYY-MM-DD). Defaults to today. Example: 2026-06-30

Body Parameters

from   string  optional    

Must be a valid date. Example: 2026-08-18T12:48:29

to   string  optional    

Must be a valid date. Example: 2026-08-18T12:48:29

Get a sent email

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/emails/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Send an email

requires authentication

Sends an email as from (which must be on a verified sending domain for your organisation) and records it in the send log. The message is DKIM-signed with the domain's key.

Example request:
curl --request POST \
    "http://localhost/api/v1/emails" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"Acme <hello@mail.acme.com>\",
    \"to\": [
        \"ali@example.com\"
    ],
    \"subject\": \"Your receipt\",
    \"html\": \"<h1>Thanks!<\\/h1>\",
    \"text\": \"Thanks!\",
    \"template\": \"order-receipt\",
    \"variables\": {
        \"order_id\": \"1042\"
    },
    \"cc\": [
        \"architecto\"
    ],
    \"bcc\": [
        \"architecto\"
    ],
    \"reply_to\": \"support@mail.acme.com\",
    \"headers\": []
}"
const url = new URL(
    "http://localhost/api/v1/emails"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "Acme <hello@mail.acme.com>",
    "to": [
        "ali@example.com"
    ],
    "subject": "Your receipt",
    "html": "<h1>Thanks!<\/h1>",
    "text": "Thanks!",
    "template": "order-receipt",
    "variables": {
        "order_id": "1042"
    },
    "cc": [
        "architecto"
    ],
    "bcc": [
        "architecto"
    ],
    "reply_to": "support@mail.acme.com",
    "headers": []
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": "01H...",
        "status": "sent",
        "subject": "Your receipt"
    }
}
 

Example response (404):


{
    "message": "Email template not found."
}
 

Example response (422):


{
    "message": "No verified sending domain owns the From address ..."
}
 

Request      

POST api/v1/emails

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

from   string     

Sender, Name <addr@domain> or a bare address on a verified domain. Example: Acme <hello@mail.acme.com>

to   string[]     

One or more recipient addresses.

subject   string  optional    

The subject line. Ignored (in favour of the rendered subject) when template is given. Example: Your receipt

html   string  optional    

HTML body (at least one of html/text/template required). Example: <h1>Thanks!</h1>

text   string  optional    

Plain-text body. Example: Thanks!

template   string  optional    

An email template slug or ULID. When given, the template's subject/html/text are rendered with variables and used instead of subject/html/text. Example: order-receipt

variables   object  optional    

Variables substituted into {{ key }} placeholders in the template. Only meaningful together with template.

cc   string[]  optional    

Optional CC addresses.

bcc   string[]  optional    

Optional BCC addresses.

reply_to   string  optional    

Optional Reply-To address. Example: support@mail.acme.com

headers   object  optional    

Optional extra headers, as a string→string map.

Email suppressions

The org's email suppression list — addresses we won't mail (hard bounces, complaints, or manual entries). Managed automatically by delivery events; this surface lets you read it and add/remove entries.

List suppressed addresses

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/emails/suppressions?reason=bounce&per_page=50" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/suppressions"
);

const params = {
    "reason": "bounce",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/emails/suppressions

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

reason   string  optional    

Filter by reason: bounce|complaint|manual|unsubscribe. Example: bounce

per_page   integer  optional    

Page size (max 100). Example: 50

Suppress an address

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/emails/suppressions" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"address\": \"bounced@example.com\"
}"
const url = new URL(
    "http://localhost/api/v1/emails/suppressions"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "address": "bounced@example.com"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/emails/suppressions

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

address   string     

The address to suppress. Example: bounced@example.com

Remove a suppression

requires authentication

Removes by suppression id (ULID) or by the raw address.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/emails/suppressions/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/emails/suppressions/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/v1/emails/suppressions/{idOrAddress}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

idOrAddress   string     

Example: architecto

Email templates

Reusable email templates (subject/html/text with {{ variable }} placeholders) an org can send from via the Send API by referencing a template slug or ULID plus a variables map.

List email templates

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/email-templates?per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/email-templates"
);

const params = {
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/email-templates

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Page size (max 100). Example: 25

Get an email template

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/email-templates/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/email-templates/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/email-templates/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Create an email template

requires authentication

slug is auto-derived from name when omitted. At least one of subject/html/text should be supplied for the template to be useful, but none are required at creation time.

Example request:
curl --request POST \
    "http://localhost/api/v1/email-templates" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Order receipt\",
    \"slug\": \"order-receipt\",
    \"subject\": \"Your order {{ order_id }} is confirmed\",
    \"html\": \"architecto\",
    \"text\": \"architecto\",
    \"variables\": []
}"
const url = new URL(
    "http://localhost/api/v1/email-templates"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Order receipt",
    "slug": "order-receipt",
    "subject": "Your order {{ order_id }} is confirmed",
    "html": "architecto",
    "text": "architecto",
    "variables": []
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": "01H...",
        "name": "Order receipt",
        "slug": "order-receipt"
    }
}
 

Request      

POST api/v1/email-templates

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Human-readable template name. Example: Order receipt

slug   string  optional    

URL-safe slug, unique per organisation. Auto-derived from name if omitted. Example: order-receipt

subject   string  optional    

Subject line, may contain {{ variable }} placeholders. Example: Your order {{ order_id }} is confirmed

html   string  optional    

HTML body, may contain {{ variable }} placeholders. Example: architecto

text   string  optional    

Plain-text body, may contain {{ variable }} placeholders. Example: architecto

variables   object  optional    

Documented placeholder defaults/description, stored as-is.

Update an email template

requires authentication

Only the supplied fields are changed. slug may be changed explicitly but is never re-derived automatically on update.

Example request:
curl --request PATCH \
    "http://localhost/api/v1/email-templates/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"architecto\",
    \"slug\": \"architecto\",
    \"subject\": \"architecto\",
    \"html\": \"architecto\",
    \"text\": \"architecto\",
    \"variables\": []
}"
const url = new URL(
    "http://localhost/api/v1/email-templates/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "architecto",
    "slug": "architecto",
    "subject": "architecto",
    "html": "architecto",
    "text": "architecto",
    "variables": []
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/v1/email-templates/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Body Parameters

name   string  optional    

Human-readable template name. Example: architecto

slug   string  optional    

URL-safe slug, unique per organisation. Example: architecto

subject   string  optional    

Subject line. Example: architecto

html   string  optional    

HTML body. Example: architecto

text   string  optional    

Plain-text body. Example: architecto

variables   object  optional    

Documented placeholder defaults/description.

Delete an email template

requires authentication

Example request:
curl --request DELETE \
    "http://localhost/api/v1/email-templates/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/email-templates/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Request      

DELETE api/v1/email-templates/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Embed widget

Server-side API for the embed SDK. The host app (e.g. a Laravel back-end) calls this to mint a short-lived signed token for the current visitor, then hands the token to the browser, which passes it to OktaWa.boot({ token }).

The token carries the visitor's external reference + optional context claims, so the embedded widget knows who it's talking to without ever exposing the org-side signing secret to the browser.

Mint a signed widget token

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/embed-tokens" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"slug\": \"marketing-site\",
    \"subject\": \"user-12345\",
    \"context\": [],
    \"ttl_seconds\": 900
}"
const url = new URL(
    "http://localhost/api/v1/embed-tokens"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "slug": "marketing-site",
    "subject": "user-12345",
    "context": [],
    "ttl_seconds": 900
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "token": "eyJpc3MiOi...",
        "expires_in": 900,
        "slug": "marketing-site"
    }
}
 

Request      

POST api/v1/embed-tokens

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

slug   string     

The widget config slug. Example: marketing-site

subject   string     

Visitor reference (your own user id, session id, etc). Example: user-12345

context   object  optional    

Free-form context bag (max 4 KB serialized).

ttl_seconds   integer  optional    

Token lifetime, 60-86400. Defaults to 900. Example: 900

Endpoints

POST api/webhooks/email/inbound

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/email/inbound" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/email/inbound"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/email/inbound

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/webhooks/email/inbound/ses

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/email/inbound/ses" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/email/inbound/ses"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/email/inbound/ses

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/webhooks/email/events

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/email/events" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/email/events"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/email/events

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/email/open/{ulid}/{signature}

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/email/open/architecto/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/email/open/architecto/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):

Show headers
content-type: image/gif
cache-control: max-age=0, must-revalidate, no-cache, no-store, private
pragma: no-cache
expires: 0
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

GIF89a�!�,D;
 

Request      

GET api/email/open/{ulid}/{signature}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

signature   string     

Example: architecto

POST api/webhooks/telegram/{ulid}

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/telegram/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/telegram/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/telegram/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

GET api/webhooks/instagram

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/webhooks/instagram" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/instagram"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (403):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

forbidden
 

Request      

GET api/webhooks/instagram

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/webhooks/instagram

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/instagram" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/instagram"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/instagram

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/webhooks/twitter

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/webhooks/twitter" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/twitter"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (400):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "error": "missing crc_token"
}
 

Request      

GET api/webhooks/twitter

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/webhooks/twitter

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/twitter" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/twitter"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/twitter

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/webhooks/tiktok

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/tiktok" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/tiktok"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/tiktok

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/integrations/meta/config

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/integrations/meta/config" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/integrations/meta/config"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/integrations/meta/config

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/integrations/meta/embedded-signup

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/integrations/meta/embedded-signup" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"b\",
    \"waba_id\": \"n\"
}"
const url = new URL(
    "http://localhost/api/integrations/meta/embedded-signup"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "b",
    "waba_id": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/integrations/meta/embedded-signup

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

Must not be greater than 1024 characters. Example: b

waba_id   string     

Must not be greater than 64 characters. Example: n

POST api/integrations/qr/sessions

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/integrations/qr/sessions" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"display_name\": \"b\"
}"
const url = new URL(
    "http://localhost/api/integrations/qr/sessions"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "display_name": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/integrations/qr/sessions

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

display_name   string     

Must be at least 2 characters. Must not be greater than 80 characters. Example: b

GET api/integrations/qr/sessions/{ulid}

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/integrations/qr/sessions/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/integrations/qr/sessions/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/integrations/qr/sessions/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

POST api/webhooks/neoleap

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/webhooks/neoleap" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/webhooks/neoleap"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/webhooks/neoleap

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/groups

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/groups" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/groups"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/groups

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/groups/{ulid}

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/groups/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/groups/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/groups/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

POST api/v1/groups

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/groups" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subject\": \"b\",
    \"participants\": [
        \"n\"
    ],
    \"channel_id\": 16
}"
const url = new URL(
    "http://localhost/api/v1/groups"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subject": "b",
    "participants": [
        "n"
    ],
    "channel_id": 16
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/groups

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subject   string     

Must be at least 1 character. Must not be greater than 255 characters. Example: b

participants   string[]  optional    

Must be at least 6 characters. Must not be greater than 32 characters.

channel_id   integer  optional    

Example: 16

PATCH api/v1/groups/{ulid}

requires authentication

Example request:
curl --request PATCH \
    "http://localhost/api/v1/groups/architecto" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subject\": \"b\",
    \"description\": \"Et animi quos velit et fugiat.\"
}"
const url = new URL(
    "http://localhost/api/v1/groups/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subject": "b",
    "description": "Et animi quos velit et fugiat."
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/v1/groups/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Body Parameters

subject   string  optional    

Must be at least 1 character. Must not be greater than 255 characters. Example: b

description   string  optional    

Must not be greater than 2048 characters. Example: Et animi quos velit et fugiat.

POST api/v1/groups/{ulid}/participants

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/groups/architecto/participants" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"participants\": [
        \"b\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/groups/architecto/participants"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "participants": [
        "b"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/groups/{ulid}/participants

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Body Parameters

participants   string[]  optional    

Must be at least 6 characters. Must not be greater than 32 characters.

DELETE api/v1/groups/{ulid}/participants

requires authentication

Example request:
curl --request DELETE \
    "http://localhost/api/v1/groups/architecto/participants" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"participants\": [
        \"b\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/groups/architecto/participants"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "participants": [
        "b"
    ]
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

DELETE api/v1/groups/{ulid}/participants

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Body Parameters

participants   string[]  optional    

Must be at least 6 characters. Must not be greater than 96 characters.

PUT api/v1/groups/{ulid}/picture

requires authentication

Example request:
curl --request PUT \
    "http://localhost/api/v1/groups/architecto/picture" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"picture_base64\": \"bngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewtnnoqi\"
}"
const url = new URL(
    "http://localhost/api/v1/groups/architecto/picture"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "picture_base64": "bngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewtnnoqi"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PUT api/v1/groups/{ulid}/picture

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Body Parameters

picture_base64   string     

Must be at least 64 characters. Example: bngzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewtnnoqi

POST api/v1/groups/{ulid}/sync

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/groups/architecto/sync" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/groups/architecto/sync"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/v1/groups/{ulid}/sync

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Example: architecto

Messages

Public API surface for sending messages from external apps.

The integrating app provides a destination wa_id (or the ulid of an existing conversation) and the message body. We resolve / create the Contact + Conversation as needed, then hand off to SendMessageAction which queues the actual provider call.

Send a message

requires authentication

Send a free-form text or media message to a wa_id (auto-creates the contact + conversation if needed) or into an existing conversation via conversation_id. The message is queued; the response carries the queued message resource — poll GET /conversations/{id}/messages or subscribe to a webhook to track delivery status.

Example request:
curl --request POST \
    "http://localhost/api/v1/messages" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"channel_id\": \"01HZK7P5R3Q6V0YH4XJ3M8CHN1\",
    \"conversation_id\": \"01HZK7P5R3Q6V0YH4XJ3M8CONV1\",
    \"wa_id\": \"966500000000\",
    \"type\": \"text\",
    \"body\": \"Your order is on the way!\",
    \"media_url\": \"https:\\/\\/cdn.example.com\\/file.pdf\"
}"
const url = new URL(
    "http://localhost/api/v1/messages"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
    "conversation_id": "01HZK7P5R3Q6V0YH4XJ3M8CONV1",
    "wa_id": "966500000000",
    "type": "text",
    "body": "Your order is on the way!",
    "media_url": "https:\/\/cdn.example.com\/file.pdf"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "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"
    }
}
 

Example response (404):


{
    "message": "Channel not found."
}
 

Example response (422):


{
    "message": "The channel id field is required when conversation id is not present.",
    "errors": {
        "channel_id": [
            "..."
        ]
    }
}
 

Request      

POST api/v1/messages

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

channel_id   string  optional    

The channel ULID. Required when sending by wa_id. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

conversation_id   string  optional    

Reuse an existing conversation by ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CONV1

wa_id   string  optional    

Destination wa_id (E.164 without +). Required unless conversation_id is set. Example: 966500000000

type   string  optional    

The message type: text|image|document|audio|video. Defaults to "text". Example: text

body   string     

The message text or media caption. Example: Your order is on the way!

media_url   string  optional    

For image/document/audio/video — a publicly fetchable HTTPS URL. Example: https://cdn.example.com/file.pdf

OAuth

Exchanges a one-time authorization code from the Connect screen (/connect → user authorizes) for a Sanctum personal access token the external app can then use as Authorization: Bearer ...

The flow is intentionally simpler than full OAuth2: no client_secret, no PKCE. Security relies on:

Exchange code for token

requires authentication

Example request:
curl --request POST \
    "http://localhost/api/v1/oauth/token" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"4f9e2c...\",
    \"redirect_uri\": \"https:\\/\\/crm.example.com\\/oktawa\\/callback\"
}"
const url = new URL(
    "http://localhost/api/v1/oauth/token"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "4f9e2c...",
    "redirect_uri": "https:\/\/crm.example.com\/oktawa\/callback"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "access_token": "1|abc...",
        "token_type": "Bearer",
        "abilities": [
            "read",
            "send"
        ]
    }
}
 

Example response (400):


{
    "error": "invalid_grant",
    "message": "Authorization code is invalid, used, or expired."
}
 

Request      

POST api/v1/oauth/token

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string     

The one-time code from the /connect redirect. Example: 4f9e2c...

redirect_uri   string     

Must match the redirect_uri the user authorized on the consent screen. Example: https://crm.example.com/oktawa/callback

Introspect the current connection

requires authentication

Returns the abilities granted to the calling token plus the app name, the workspace it's bound to, and expiry — so a connected app can check what it may do and decide whether it needs to request more. To add abilities, send the user back through the Connect screen (build the URL with the FULL desired ability set); the consent page highlights what's new. No request body.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/oauth/introspect" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/oauth/introspect"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "app_name": "Frameo",
        "abilities": [
            "read",
            "send"
        ],
        "organization": {
            "id": "01HZK...",
            "name": "Acme"
        },
        "expires_at": "2026-10-01T00:00:00+00:00",
        "last_used_at": "2026-07-15T10:00:00+00:00"
    }
}
 

Request      

GET api/v1/oauth/introspect

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Revoke the current connection

requires authentication

Lets a connected app disconnect ITSELF: called with the app's own bearer token, it revokes that token (so all further calls 401) and fires a connection.revoked webhook to the workspace. Idempotent — a token can only be presented while it still exists. No request body.

Example request:
curl --request POST \
    "http://localhost/api/v1/oauth/revoke" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/oauth/revoke"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "revoked": true
}
 

Request      

POST api/v1/oauth/revoke

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Partner API

The integration surface for a technical partner (شريك تقني): an account whose only job is wiring your own product into Connect. Through it you create workspaces for your customers, add and manage their users, mint their API tokens, reserve channels, and drop a user straight into the dashboard — all with keys you hold yourself.

This is not the platform-operator surface. A partner token can only see workspaces it provisioned (anything else answers 404, never 403), and no endpoint here mints an administrative grant over the platform.

Mint your keys from /app/partner once your partner account is approved. Full reference: docs/PARTNER_API.md.

Exchange keys for a token

The only unauthenticated endpoint on the Partner API — the key pair in the body IS the credential. Rate limit: 10/min.

Every rejection answers the same opaque invalid_client regardless of cause, so the endpoint cannot be used to probe which client ids exist. A suspended partner gets 403 partner_suspended.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/token" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"grant_type\": \"client_credentials\",
    \"client_id\": \"okc_ci_01hzk7p5r3q6v0yh4xj3m8ws01\",
    \"client_secret\": \"okc_cs_9f2c...\",
    \"abilities\": [
        \"workspaces.read\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/partner/token"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "grant_type": "client_credentials",
    "client_id": "okc_ci_01hzk7p5r3q6v0yh4xj3m8ws01",
    "client_secret": "okc_cs_9f2c...",
    "abilities": [
        "workspaces.read"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "access_token": "okc_pat_...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-08-18T12:00:00+00:00",
    "abilities": [
        "workspaces.read",
        "workspaces.write"
    ]
}
 

Example response (401):


{
    "error": "invalid_client"
}
 

Example response (403):


{
    "error": "partner_suspended"
}
 

Request      

POST api/v1/partner/token

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

grant_type   string  optional    

The only supported grant. Example: client_credentials

client_id   string     

The public half of your key pair. Example: okc_ci_01hzk7p5r3q6v0yh4xj3m8ws01

client_secret   string     

The secret half, shown once when the key was created. Example: okc_cs_9f2c...

abilities   string[]  optional    

Narrow the token to a subset of the key's abilities. Never widens them.

Who am I

requires authentication

Introspection: the partner this token belongs to, what it may do, when it expires, and how many workspaces you own. Call it on boot to confirm a token is live before running a provisioning sequence.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/me" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/me"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "partner": {
            "id": "01HZK...",
            "name": "Acme Technologies",
            "slug": "acme",
            "status": "active"
        },
        "token": {
            "name": "exchange:integration key",
            "kind": "exchanged",
            "abilities": [
                "workspaces.read",
                "workspaces.write"
            ],
            "expires_at": "2026-08-18T12:00:00+00:00"
        },
        "workspaces_count": 12
    }
}
 

Request      

GET api/v1/partner/me

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

List workspaces

requires authentication

Only workspaces you provisioned. Requires workspaces.read.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/workspaces?search=acme&external_id=acct_8891&status=active&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces"
);

const params = {
    "search": "acme",
    "external_id": "acct_8891",
    "status": "active",
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/partner/workspaces

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

search   string  optional    

Match on name or slug substring. Example: acme

external_id   string  optional    

Find the workspace by your own identifier. Example: acct_8891

status   string  optional    

Filter by status. Example: active

per_page   integer  optional    

Page size, 1..100. Defaults to 25. Example: 25

Get a workspace

requires authentication

Requires workspaces.read.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):


{
    "error": "workspace_not_found"
}
 

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 118
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/partner/workspaces/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Create a workspace

requires authentication

Requires workspaces.write. Answers 201 on creation and 200 when an external_id you already used returns the existing workspace — so a retry never mints a duplicate tenant.

Pass owner to install the workspace owner in the same call; password_auto returns a one_time_password on this response only.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Acme Support\",
    \"slug\": \"acme-support\",
    \"external_id\": \"acct_8891\",
    \"locale\": \"ar\",
    \"timezone\": \"Asia\\/Riyadh\",
    \"country\": \"SA\",
    \"metadata\": [],
    \"owner\": {
        \"name\": \"Sara Admin\",
        \"email\": \"sara@acme.test\",
        \"password\": \"|]|{+-\",
        \"password_auto\": true
    }
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Support",
    "slug": "acme-support",
    "external_id": "acct_8891",
    "locale": "ar",
    "timezone": "Asia\/Riyadh",
    "country": "SA",
    "metadata": [],
    "owner": {
        "name": "Sara Admin",
        "email": "sara@acme.test",
        "password": "|]|{+-",
        "password_auto": true
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Display name. Example: Acme Support

slug   string  optional    

URL-safe slug. A collision gets a numeric suffix, so you never have to solve uniqueness. Example: acme-support

external_id   string  optional    

Your own identifier for this account. Makes retries idempotent. Example: acct_8891

locale   string  optional    

Two-letter locale. Example: ar

timezone   string  optional    

IANA timezone. Example: Asia/Riyadh

country   string  optional    

Two-letter country code. Example: SA

metadata   object  optional    

Free-form metadata bag stored with the workspace.

owner   object  optional    

The workspace owner to create alongside it.

name   string     

Owner's full name. Example: Sara Admin

email   string     

Owner's email. An existing account is reused, never overwritten. Example: sara@acme.test

password   string  optional    

Min 12 characters. Omit and pass password_auto instead to have one generated. Example: |]|{+-

password_auto   boolean  optional    

Generate the password and return it once. Example: true

Update a workspace

requires authentication

Requires workspaces.write. The slug is immutable once issued — integrations key their own records off it.

Example request:
curl --request PATCH \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Acme Support\",
    \"locale\": \"en\",
    \"timezone\": \"Asia\\/Riyadh\",
    \"country\": \"SA\",
    \"metadata\": []
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme Support",
    "locale": "en",
    "timezone": "Asia\/Riyadh",
    "country": "SA",
    "metadata": []
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PATCH api/v1/partner/workspaces/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Body Parameters

name   string  optional    

Display name. Example: Acme Support

locale   string  optional    

Two-letter locale. Example: en

timezone   string  optional    

IANA timezone. Example: Asia/Riyadh

country   string  optional    

Two-letter country code. Example: SA

metadata   object  optional    

Replaces the stored metadata bag. Send null to clear it.

Suspend a workspace

requires authentication

Your kill switch for an account that churned inside your product. Requires workspaces.write.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/suspend" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/suspend"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces/{ulid}/suspend

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Reactivate a workspace

requires authentication

Requires workspaces.write.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/activate" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/activate"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces/{ulid}/activate

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

List workspace members

requires authentication

Requires users.read.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 117
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/partner/workspaces/{ulid}/users

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Add a member

requires authentication

Requires users.write. Answers 201 for a newly created user and 200 when the email already belongs to a Connect account — that account is reused as a member and its password is never overwritten.

Adding someone as owner transfers workspace ownership to them.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Khalid Agent\",
    \"email\": \"khalid@acme.test\",
    \"password\": \"|]|{+-\",
    \"password_auto\": true,
    \"locale\": \"ar\",
    \"role\": \"agent\"
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Khalid Agent",
    "email": "khalid@acme.test",
    "password": "|]|{+-",
    "password_auto": true,
    "locale": "ar",
    "role": "agent"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces/{ulid}/users

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Body Parameters

name   string     

Full name. Example: Khalid Agent

email   string     

Email address. Example: khalid@acme.test

password   string  optional    

Min 12 characters. Omit and pass password_auto to have one generated. Example: |]|{+-

password_auto   boolean  optional    

Generate the password and return it once as one_time_password. Example: true

locale   string  optional    

Two-letter locale for the new user. Example: ar

role   string  optional    

One of owner, admin, manager, agent, member. Defaults to admin. Example: agent

Update a member

requires authentication

Requires users.write. The workspace owner cannot be suspended — hand ownership to someone else first.

Example request:
curl --request PATCH \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users/01HZK7P5R3Q6V0YH4XJ3M8USR01" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"role\": \"manager\",
    \"status\": \"suspended\"
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users/01HZK7P5R3Q6V0YH4XJ3M8USR01"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "role": "manager",
    "status": "suspended"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (422):


{
    "error": "owner_cannot_be_suspended"
}
 

Request      

PATCH api/v1/partner/workspaces/{ulid}/users/{userUlid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

userUlid   string     

Member id. Example: 01HZK7P5R3Q6V0YH4XJ3M8USR01

Body Parameters

role   string  optional    

One of owner, admin, manager, agent, member. Example: manager

status   string  optional    

active or suspended. Example: suspended

Remove a member

requires authentication

Requires users.write. Drops the membership; the user account itself survives, since it may belong to workspaces you know nothing about. The owner cannot be removed — transfer ownership first.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users/01HZK7P5R3Q6V0YH4XJ3M8USR01" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/users/01HZK7P5R3Q6V0YH4XJ3M8USR01"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "deleted": true
}
 

Example response (422):


{
    "error": "owner_cannot_be_removed"
}
 

Request      

DELETE api/v1/partner/workspaces/{ulid}/users/{userUlid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

userUlid   string     

Member id. Example: 01HZK7P5R3Q6V0YH4XJ3M8USR01

List workspace tokens

requires authentication

Metadata only — no secrets. Requires tokens.write.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 116
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/partner/workspaces/{ulid}/tokens

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Mint a workspace token

requires authentication

Requires tokens.write. The plain token is returned exactly once — Connect keeps only a hash.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Acme product sync\",
    \"user_id\": \"01HZK7P5R3Q6V0YH4XJ3M8USR01\",
    \"abilities\": [
        \"read\",
        \"send\"
    ],
    \"expires_at\": \"2026-12-01T00:00:00Z\"
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Acme product sync",
    "user_id": "01HZK7P5R3Q6V0YH4XJ3M8USR01",
    "abilities": [
        "read",
        "send"
    ],
    "expires_at": "2026-12-01T00:00:00Z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces/{ulid}/tokens

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Body Parameters

name   string     

Human-readable label. Example: Acme product sync

user_id   string     

Id of the workspace member the token belongs to. Example: 01HZK7P5R3Q6V0YH4XJ3M8USR01

abilities   string[]     

Subset of read, write, send, webhooks.

expires_at   string  optional    

Optional ISO 8601 expiry, must be in the future. Example: 2026-12-01T00:00:00Z

Revoke a workspace token

requires authentication

Requires tokens.write.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens/123" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/tokens/123"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "revoked": true
}
 

Request      

DELETE api/v1/partner/workspaces/{ulid}/tokens/{tokenId}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

tokenId   integer     

Numeric token id from the list endpoint. Example: 123

List workspace channels

requires authentication

Requires channels.read.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/channels" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/channels"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 115
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/partner/workspaces/{ulid}/channels

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Reserve a channel

requires authentication

Requires channels.write. Creates the channel disconnected, with no provider credentials attached.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/channels" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"display_name\": \"Sales line\",
    \"type\": \"cloud_api\",
    \"phone_number\": \"+966500000000\"
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/channels"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "display_name": "Sales line",
    "type": "cloud_api",
    "phone_number": "+966500000000"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/partner/workspaces/{ulid}/channels

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Body Parameters

display_name   string     

Human-friendly name. Example: Sales line

type   string     

Channel type: cloud_api, baileys, telegram, instagram_dm, twitter, linkedin, tiktok, email, embed. Example: cloud_api

phone_number   string  optional    

E.164 with leading +. Example: +966500000000

Issue a one-time sign-in link

requires authentication

Send the member to the returned url and they land signed in, on the workspace you named. Requires sso.issue.

The ticket is opaque and random, single-use (redeeming deletes it), and expires in five minutes. Membership is re-checked at redemption, so a member you removed in the meantime cannot use it.

Example request:
curl --request POST \
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/sso" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"user_id\": \"01HZK7P5R3Q6V0YH4XJ3M8USR01\",
    \"redirect\": \"\\/app\\/inbox\"
}"
const url = new URL(
    "http://localhost/api/v1/partner/workspaces/01HZK7P5R3Q6V0YH4XJ3M8WS001/sso"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "user_id": "01HZK7P5R3Q6V0YH4XJ3M8USR01",
    "redirect": "\/app\/inbox"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "url": "https://connect.getokta.io/partner/sso?ticket=...",
        "expires_in": 300
    }
}
 

Example response (422):


{
    "error": "not_an_active_member"
}
 

Request      

POST api/v1/partner/workspaces/{ulid}/sso

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

Workspace id. Example: 01HZK7P5R3Q6V0YH4XJ3M8WS001

Body Parameters

user_id   string     

Id of an active member of this workspace. Example: 01HZK7P5R3Q6V0YH4XJ3M8USR01

redirect   string  optional    

Where to land. Must start with /app/; anything else falls back to /app. Example: /app/inbox

Social posts

Compose a post and fan it out to one or more social channels (Telegram, X, Instagram, …). A future scheduled_at schedules the post; otherwise it is created as a draft.

List social posts

requires authentication

Returns a paginated list of social posts with their per-channel targets, newest first.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/social-posts?status=scheduled&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/social-posts"
);

const params = {
    "status": "scheduled",
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "01HZK7P5R3Q6V0YH4XJ3M8PST1",
            "status": "scheduled",
            "body": "New drop is live!",
            "media": [],
            "options": null,
            "scheduled_at": "2026-07-05T09:00:00+00:00",
            "published_at": null,
            "target_count": 1,
            "published_count": 0,
            "failed_count": 0,
            "created_at": "2026-07-04T08:00:00+00:00",
            "targets": [
                {
                    "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
                    "status": "pending",
                    "target_ref": null,
                    "permalink": null,
                    "provider_post_id": null,
                    "published_at": null
                }
            ]
        }
    ],
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "total": 1
    }
}
 

Request      

GET api/v1/social-posts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status (draft, scheduled, publishing, published, partially_failed, failed, cancelled). Example: scheduled

per_page   integer  optional    

Page size, 1-100. Defaults to 25. Example: 25

Get a social post

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/social-posts/01HZK7P5R3Q6V0YH4XJ3M8PST1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/social-posts/01HZK7P5R3Q6V0YH4XJ3M8PST1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):


{
    "message": "No query results for model [SocialPost]."
}
 

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/social-posts/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

The social post ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8PST1

Schedule a social post

requires authentication

Composes a post and fans it out to the given channels. Every channel must support feed/story publishing. With a future scheduled_at the post is scheduled; without one it is created as a draft.

Example request:
curl --request POST \
    "http://localhost/api/v1/social-posts" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"text\": \"New drop is live!\",
    \"channel_ids\": [
        \"01HZK7P5R3Q6V0YH4XJ3M8CHN1\"
    ],
    \"scheduled_at\": \"2026-07-05T09:00:00+00:00\",
    \"media\": [
        {
            \"url\": \"https:\\/\\/cdn.example.com\\/promo.jpg\",
            \"type\": \"image\"
        }
    ]
}"
const url = new URL(
    "http://localhost/api/v1/social-posts"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "text": "New drop is live!",
    "channel_ids": [
        "01HZK7P5R3Q6V0YH4XJ3M8CHN1"
    ],
    "scheduled_at": "2026-07-05T09:00:00+00:00",
    "media": [
        {
            "url": "https:\/\/cdn.example.com\/promo.jpg",
            "type": "image"
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8PST1",
        "status": "scheduled",
        "body": "New drop is live!",
        "target_count": 1,
        "scheduled_at": "2026-07-05T09:00:00+00:00"
    }
}
 

Example response (422):


{
    "message": "One or more channels were not found.",
    "errors": {
        "channel_ids": [
            "One or more channels were not found."
        ]
    }
}
 

Request      

POST api/v1/social-posts

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

text   string     

The post caption/body. Example: New drop is live!

channel_ids   string[]     

Channel ULIDs to publish to.

scheduled_at   string  optional    

ISO-8601 publish time; omit for a draft. Example: 2026-07-05T09:00:00+00:00

media   object[]  optional    

Media items to attach.

url   string  optional    

A publicly fetchable HTTPS URL. Example: https://cdn.example.com/promo.jpg

type   string  optional    

Media type hint (image, video). Example: image

Tags

List the organization's tags and apply tags to a contact. Applying tags is idempotent — tag slugs that don't exist yet are created on the fly.

List tags

requires authentication

Returns a paginated list of tags, ordered by name.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/tags?scope=contact&per_page=50" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/tags"
);

const params = {
    "scope": "contact",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "01HZK7P5R3Q6V0YH4XJ3M8TAG1",
            "name": "VIP",
            "slug": "vip",
            "color": "#f59e0b",
            "scope": "contact",
            "created_at": "2026-07-04T08:00:00+00:00"
        }
    ],
    "meta": {
        "current_page": 1,
        "per_page": 50,
        "total": 1
    }
}
 

Request      

GET api/v1/tags

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

scope   string  optional    

Filter by scope (contact, conversation). Example: contact

per_page   integer  optional    

Page size, 1-100. Defaults to 50. Example: 50

Apply tags to a contact

requires authentication

Attaches the given tags to the contact, creating any that don't exist yet. Existing attachments are left untouched (idempotent). Returns the contact with its full tag list.

Example request:
curl --request POST \
    "http://localhost/api/v1/contacts/01HZK7P5R3Q6V0YH4XJ3M8AAA1/tags" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"tags\": [
        \"vip\",
        \"riyadh\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/contacts/01HZK7P5R3Q6V0YH4XJ3M8AAA1/tags"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "tags": [
        "vip",
        "riyadh"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8AAA1",
        "wa_id": "966500000000",
        "name": "Ali",
        "tags": [
            "vip",
            "riyadh"
        ]
    }
}
 

Example response (404):


{
    "message": "No query results for model [Contact]."
}
 

Example response (422):


{
    "message": "The tags field is required.",
    "errors": {
        "tags": [
            "The tags field is required."
        ]
    }
}
 

Request      

POST api/v1/contacts/{id}/tags

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The contact ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1

Body Parameters

tags   string[]     

Tag names/slugs to apply.

Templates

List templates

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/templates?status=APPROVED&language=ar" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/templates"
);

const params = {
    "status": "APPROVED",
    "language": "ar",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/templates

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by Meta status (DRAFT, PENDING, APPROVED, REJECTED, PAUSED, DISABLED). Example: APPROVED

language   string  optional    

Filter by language code. Example: ar

Send a template message

requires authentication

Send a Meta-approved template to a wa_id with optional positional variables (1-indexed in Meta, 0-indexed in the array — variable 1 = $variables[0]).

Example request:
curl --request POST \
    "http://localhost/api/v1/templates/send" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"channel_id\": \"01HZK7P5R3Q6V0YH4XJ3M8CHN1\",
    \"wa_id\": \"966500000000\",
    \"template_name\": \"order_ready\",
    \"language\": \"ar\",
    \"variables\": [
        \"12345\",
        \"120 SAR\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/templates/send"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "channel_id": "01HZK7P5R3Q6V0YH4XJ3M8CHN1",
    "wa_id": "966500000000",
    "template_name": "order_ready",
    "language": "ar",
    "variables": [
        "12345",
        "120 SAR"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8MSG2",
        "type": "template",
        "status": "queued"
    }
}
 

Example response (404):


{
    "message": "Template not found."
}
 

Request      

POST api/v1/templates/send

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

channel_id   string     

Channel ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8CHN1

wa_id   string     

Destination wa_id. Example: 966500000000

template_name   string     

Template name as registered with Meta. Example: order_ready

language   string  optional    

Template language code. Defaults to the template's primary language. Example: ar

variables   string[]  optional    

Positional variable values for the body.

Tickets

Support tickets inside the authenticated organization. Open a ticket in a pipeline, then transition it between stages.

List tickets

requires authentication

Returns a paginated list of tickets, newest first. Filter by the current stage category or by pipeline.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/tickets?status=open&pipeline_id=01HZK7P5R3Q6V0YH4XJ3M8PIP1&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/tickets"
);

const params = {
    "status": "open",
    "pipeline_id": "01HZK7P5R3Q6V0YH4XJ3M8PIP1",
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "01HZK7P5R3Q6V0YH4XJ3M8TKT1",
            "number": 1001,
            "subject": "Order not delivered",
            "description": null,
            "priority": "normal",
            "status": "open",
            "stage_id": "01HZK7P5R3Q6V0YH4XJ3M8STG1",
            "stage": "New",
            "pipeline_id": "01HZK7P5R3Q6V0YH4XJ3M8PIP1",
            "contact_id": null,
            "assigned_user_id": null,
            "created_at": "2026-07-04T08:00:00+00:00"
        }
    ],
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "total": 1
    }
}
 

Request      

GET api/v1/tickets

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by stage category (open, in_progress, on_hold, resolved, closed). Example: open

pipeline_id   string  optional    

Filter by pipeline ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8PIP1

per_page   integer  optional    

Page size, 1-100. Defaults to 25. Example: 25

Get a ticket

requires authentication

Example request:
curl --request GET \
    --get "http://localhost/api/v1/tickets/01HZK7P5R3Q6V0YH4XJ3M8TKT1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/tickets/01HZK7P5R3Q6V0YH4XJ3M8TKT1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):


{
    "message": "No query results for model [Ticket]."
}
 

Example response (500):

Show headers
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
strict-transport-security: max-age=63072000; includeSubDomains; preload
access-control-allow-origin: *
 

{
    "message": "Server Error"
}
 

Request      

GET api/v1/tickets/{ulid}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

The ticket ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8TKT1

Open a ticket

requires authentication

Opens a ticket in the given pipeline (or the organization's default pipeline) at its first stage, applying the active SLA policy.

Example request:
curl --request POST \
    "http://localhost/api/v1/tickets" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subject\": \"Order not delivered\",
    \"description\": \"Customer says parcel never arrived.\",
    \"pipeline_id\": \"01HZK7P5R3Q6V0YH4XJ3M8PIP1\",
    \"contact_id\": \"01HZK7P5R3Q6V0YH4XJ3M8AAA1\",
    \"priority\": \"high\"
}"
const url = new URL(
    "http://localhost/api/v1/tickets"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subject": "Order not delivered",
    "description": "Customer says parcel never arrived.",
    "pipeline_id": "01HZK7P5R3Q6V0YH4XJ3M8PIP1",
    "contact_id": "01HZK7P5R3Q6V0YH4XJ3M8AAA1",
    "priority": "high"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8TKT1",
        "number": 1001,
        "subject": "Order not delivered",
        "priority": "high",
        "status": "open",
        "stage_id": "01HZK7P5R3Q6V0YH4XJ3M8STG1",
        "pipeline_id": "01HZK7P5R3Q6V0YH4XJ3M8PIP1"
    }
}
 

Example response (404):


{
    "message": "No query results for model [TicketPipeline]."
}
 

Example response (422):


{
    "message": "No ticket pipeline is available.",
    "errors": {
        "pipeline_id": [
            "No ticket pipeline is available."
        ]
    }
}
 

Request      

POST api/v1/tickets

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subject   string     

Short summary of the issue. Example: Order not delivered

description   string  optional    

Longer description. Example: Customer says parcel never arrived.

pipeline_id   string  optional    

Pipeline ULID. Defaults to the org's default pipeline. Example: 01HZK7P5R3Q6V0YH4XJ3M8PIP1

contact_id   string  optional    

Related contact ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1

priority   string  optional    

Priority: low|normal|high|urgent. Defaults to "normal". Example: high

Transition a ticket to another stage

requires authentication

Moves the ticket to another stage within its own pipeline. Resolving to a resolved/closed category stamps the corresponding timestamp.

Example request:
curl --request POST \
    "http://localhost/api/v1/tickets/01HZK7P5R3Q6V0YH4XJ3M8TKT1/transition" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"stage_id\": \"01HZK7P5R3Q6V0YH4XJ3M8STG2\",
    \"note\": \"Escalated to logistics.\"
}"
const url = new URL(
    "http://localhost/api/v1/tickets/01HZK7P5R3Q6V0YH4XJ3M8TKT1/transition"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "stage_id": "01HZK7P5R3Q6V0YH4XJ3M8STG2",
    "note": "Escalated to logistics."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8TKT1",
        "status": "resolved",
        "stage_id": "01HZK7P5R3Q6V0YH4XJ3M8STG2",
        "resolved_at": "2026-07-04T09:00:00+00:00"
    }
}
 

Example response (404):


{
    "message": "No query results for model [TicketStage]."
}
 

Example response (422):


{
    "message": "The stage id field is required.",
    "errors": {
        "stage_id": [
            "The stage id field is required."
        ]
    }
}
 

Request      

POST api/v1/tickets/{ulid}/transition

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

ulid   string     

The ticket ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8TKT1

Body Parameters

stage_id   string     

Target stage ULID (must belong to the ticket's pipeline). Example: 01HZK7P5R3Q6V0YH4XJ3M8STG2

note   string  optional    

Optional note describing the transition. Example: Escalated to logistics.

Webhooks

Register outbound webhook subscriptions programmatically instead of adding them by hand in the dashboard. Deliveries are POSTed to your URL with an X-Okta-Signature: sha256=<hmac> header (HMAC-SHA256 of the raw body using the subscription secret) plus X-Okta-Event and X-Okta-Delivery.

List webhook subscriptions

requires authentication

Returns the active organization's webhook subscriptions, newest first. The signing secret is never returned here — it is shown only once, at creation time.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/webhooks?per_page=50" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/webhooks"
);

const params = {
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": "01HZK7P5R3Q6V0YH4XJ3M8AAA1",
            "name": "Order events",
            "url": "https://example.test/hooks/okta",
            "events": [
                "subscription.expired",
                "channel.deleted"
            ],
            "is_active": true,
            "max_attempts": 5
        }
    ],
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "total": 1
    }
}
 

Request      

GET api/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Page size, between 1 and 100. Defaults to 25. Example: 50

Create a webhook subscription

requires authentication

Registers a new endpoint. A signing secret is generated and returned in the secret field of THIS response ONLY — store it now to verify the X-Okta-Signature header; it can never be read again.

Example request:
curl --request POST \
    "http://localhost/api/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Order events\",
    \"url\": \"https:\\/\\/example.test\\/hooks\\/okta\",
    \"events\": [
        \"subscription.expired\",
        \"channel.deleted\"
    ],
    \"max_attempts\": 5
}"
const url = new URL(
    "http://localhost/api/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Order events",
    "url": "https:\/\/example.test\/hooks\/okta",
    "events": [
        "subscription.expired",
        "channel.deleted"
    ],
    "max_attempts": 5
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "01HZK7P5R3Q6V0YH4XJ3M8AAA1",
        "name": "Order events",
        "url": "https://example.test/hooks/okta",
        "events": [
            "subscription.expired"
        ],
        "is_active": true,
        "max_attempts": 5,
        "secret": "shown-once-store-it-now"
    }
}
 

Request      

POST api/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

A label for the subscription. Example: Order events

url   string     

HTTPS endpoint that receives deliveries. Example: https://example.test/hooks/okta

events   string[]     

Event names to receive, or ["*"] for all.

max_attempts   integer  optional    

Delivery attempts before giving up (1–20). Defaults to 5. Example: 5

Delete a webhook subscription

requires authentication

Example request:
curl --request DELETE \
    "http://localhost/api/v1/webhooks/01HZK7P5R3Q6V0YH4XJ3M8AAA1" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/webhooks/01HZK7P5R3Q6V0YH4XJ3M8AAA1"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "deleted": true
}
 

Example response (404):


{
    "message": "No query results for model [WebhookSubscription]."
}
 

Request      

DELETE api/v1/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The subscription ULID. Example: 01HZK7P5R3Q6V0YH4XJ3M8AAA1