sikio Developers
Documentation menu

Sikio API · v1

API reference

Version 1 · HTTP Basic authentication · workspace-scoped credentials

Production base: https://api.ictlife.com/v1
Staging base: https://stage.api.ictlife.com/v1

Download OpenAPI 3.0 JSON ↓

Make your first request

Copy a request and replace YOUR_API_KEY and YOUR_API_SECRET. Channel discovery, contact listing and profile-field discovery need no other inputs. Other endpoints show the IDs, message details or file you need to replace.

Examples use staging. For production, change stage.api.ictlife.com to api.ictlife.com and use production credentials. Response values are illustrative; use IDs returned by your own workspace.

Running examples and keeping credentials secure

Paste cURL into your terminal, save JavaScript as request.mjs and run node request.mjs (Node.js 22+), or save Python as request.py and run python3 request.py (Python 3, no extra packages). Choose one language per operation. Before substituting real credentials, read the private-folder, owner-only file permissions, shell-history and temporary-file cleanup guidance. Sends and contact writes change your workspace, so use designated test data.

GET/v1/integrations/whatsapp/channels

List connected WhatsApp channels

Required scope: whatsapp:read

Request

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels'

Response

HTTP 200
{
  "data": [
    {
      "id": 42,
      "name": "Customer service",
      "phone_number_id": "WHATSAPP_PHONE_NUMBER_ID"
    }
  ]
}

Eligible native WhatsApp channels in this workspace.

Parameters, fields and errors

Response fields: WhatsappChannelList.

Common errors · Delivery and safe retries

GET/v1/integrations/whatsapp/channels/{channel_id}/templates

List WhatsApp templates

Inspect status, components and placeholder names. Only an APPROVED template in this channel’s WhatsApp Business Account can be sent.

Required scope: whatsapp:read

Request

Replace CHANNEL_ID with your channel’s id.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates'

Response

HTTP 200
{
  "data": [
    {
      "id": "YOUR_APPROVED_TEMPLATE_ID",
      "name": "order_ready",
      "language": "en",
      "status": "APPROVED",
      "components": [
        {
          "type": "BODY",
          "text": "Your order is ready for collection."
        }
      ]
    }
  ],
  "paging": {
    "has_more": false
  }
}

Template definitions and cursor pagination.

Parameters, fields and errors

Parameters

NameInRequiredDescription
channel_idpathYes integer
afterqueryNoCursor from paging.cursors.after. Continue while paging.has_more is true. string

Response fields: TemplateList.

Common errors · Delivery and safe retries

POST/v1/integrations/whatsapp/channels/{channel_id}/messages

Send an approved template

Uses the discovered numeric Sikio channel ID. Maximum JSON body is 32 KiB. Unsupported template components, missing or surplus variables fail before provider sending.

Required scope: whatsapp:send

Request

Replace CHANNEL_ID with your channel’s id. Use your recipient and approved template ID. Add variables or media only when that template needs them. Replace UNIQUE_MESSAGE_KEY for each new message; keep it unchanged when retrying that message.

This request uses a fixed-text template with no variables.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: UNIQUE_MESSAGE_KEY' \
  --data-binary @- \
  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages' <<'JSON'
{
  "recipient": "+254700000001",
  "template_id": "YOUR_APPROVED_TEMPLATE_ID"
}
JSON

Response

HTTP 202
{
  "id": 1042,
  "channel_id": 42,
  "status": "accepted",
  "conversation_id": "CONVERSATION_ID",
  "message_id": "MESSAGE_ID",
  "provider_message_id": "wamid.EXAMPLE",
  "created_at": "2026-10-16T09:00:00Z",
  "updated_at": "2026-10-16T09:00:01Z"
}

Request is accepted, processing or uncertain. Inspect status; HTTP acceptance does not confirm delivery.

Parameters, fields and errors

Parameters

NameInRequiredDescription
channel_idpathYes integer
Idempotency-KeyheaderYesUnique per intended send. Retry with the same credential, channel, key and identical payload. string

Request fields: TemplateSend.

Response fields: WhatsappMessage.

HTTPMeaningBody
409This credential already used the idempotency key for a different payload or channel.—
422Preparation failed before a send or WhatsApp explicitly rejected it. The failed request retains its ID and retry key.WhatsappMessage
429New-send limit reached: 60 per minute per credential across replicas. Identical replays do not consume another slot.—

Common errors · Delivery and safe retries

POST/v1/integrations/whatsapp/channels/{channel_id}/media

Upload an optional media asset

Only needed when supplying a new asset for a media template. Exactly one multipart file: JPEG/PNG up to 5 MiB, MP4 up to 16 MiB, PDF up to 100 MiB. No URL upload or extra fields. Uploads are not idempotent.

Required scope: whatsapp:send

Request

Replace CHANNEL_ID with your channel’s id. Use a local PNG file at ./header-image.png, or change the file and MIME type together.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  --request POST \
  --form 'file=@./header-image.png;type=image/png' \
  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/media'

Response

HTTP 201
{
  "media_id": "WHATSAPP_MEDIA_ID",
  "channel_id": 42,
  "mime_type": "image/png",
  "size_bytes": 24576
}

Sendable WhatsApp media ID; upload alone sends no message.

Parameters, fields and errors

Parameters

NameInRequiredDescription
channel_idpathYes integer

Request fields

FieldTypeRequired in objectDescription
filestringYes

Response fields: UploadedMedia.

HTTPMeaningBody
413The file or request is too large.—
429Upload concurrency exceeded; retry after the supplied delay.—
502Provider upload failed; no media ID was confirmed.—

Common errors · Delivery and safe retries

GET/v1/integrations/whatsapp/messages/{request_id}

Get a message request and receipts

Use the original sending credential with whatsapp:read. A rotated key cannot retrieve the previous credential’s requests. Polling is supported; outbound webhook subscriptions are not included.

Required scope: whatsapp:read

Request

Replace REQUEST_ID with the send response’s id.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID'

Response

HTTP 200
{
  "id": 1042,
  "channel_id": 42,
  "status": "accepted",
  "conversation_id": "CONVERSATION_ID",
  "message_id": "MESSAGE_ID",
  "provider_message_id": "wamid.EXAMPLE",
  "external_reference": "ORDER-1042",
  "created_at": "2026-10-16T09:00:00Z",
  "updated_at": "2026-10-16T09:00:01Z"
}

Request belonging to this workspace and the original API credential.

Parameters, fields and errors

Parameters

NameInRequiredDescription
request_idpathYes integer

Response fields: WhatsappMessage.

Common errors · Delivery and safe retries

GET/v1/integrations/contacts

List and search contacts

Required scope: contacts:read

Request

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/contacts'

Response

HTTP 200
{
  "contacts": [
    {
      "id": 91,
      "display_name": "Amina Customer",
      "whatsapp_number": "+254700000001",
      "email": "amina@example.com",
      "consent_status": "unknown"
    }
  ],
  "pagination": {
    "count": 1,
    "page": 1,
    "per": 20
  }
}

Contacts and page-based pagination.

Parameters, fields and errors

Parameters

NameInRequiredDescription
pagequeryNoPage number. integer
perqueryNoPage size; capped at 100. integer; maximum 100
searchqueryNoContact search text. string

Response fields: ContactList.

Common errors · Delivery and safe retries

POST/v1/integrations/contacts

Create a contact

Supply a name or usable identity from which a display name can be derived. Creating a contact is not consent to send a message.

Required scope: contacts:write

Request

Replace the sample name, WhatsApp number and email with your contact’s details. Set consent_status from recorded consent; creating a contact does not grant permission to message them.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  --request POST \
  --header 'Content-Type: application/json' \
  --data-binary @- \
  'https://stage.api.ictlife.com/v1/integrations/contacts' <<'JSON'
{
  "display_name": "Amina Customer",
  "whatsapp_number": "+254700000001",
  "email": "amina@example.com",
  "consent_status": "unknown"
}
JSON

Response

HTTP 201
{
  "id": 91,
  "display_name": "Amina Customer",
  "whatsapp_number": "+254700000001",
  "email": "amina@example.com",
  "consent_status": "unknown"
}

Created contact.

Parameters, fields and errors

Request fields: ContactInput.

Response fields: Contact.

Common errors · Delivery and safe retries

POST/v1/integrations/contacts/upsert

Create or update by contact identity

Match by normalized phone, WhatsApp or email identity. Supply at least one matching identity. Conflicting identities are rejected instead of merging contacts automatically.

Required scope: contacts:write

Request

Replace the sample name, WhatsApp number and email with your contact’s details. Set consent_status from recorded consent; creating a contact does not grant permission to message them.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  --request POST \
  --header 'Content-Type: application/json' \
  --data-binary @- \
  'https://stage.api.ictlife.com/v1/integrations/contacts/upsert' <<'JSON'
{
  "display_name": "Amina Customer",
  "whatsapp_number": "+254700000001",
  "email": "amina@example.com",
  "consent_status": "unknown"
}
JSON

Response

HTTP 200
{
  "action": "create",
  "contact": {
    "id": 91,
    "display_name": "Amina Customer",
    "whatsapp_number": "+254700000001",
    "email": "amina@example.com",
    "consent_status": "unknown"
  }
}

Created or updated contact.

Parameters, fields and errors

Request fields: ContactInput.

Response fields: ContactUpsertResult.

Common errors · Delivery and safe retries

GET/v1/integrations/contacts/{contact_id}

Get a contact

Required scope: contacts:read

Request

Replace CONTACT_ID with your contact’s id.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID'

Response

HTTP 200
{
  "id": 91,
  "display_name": "Amina Customer",
  "whatsapp_number": "+254700000001",
  "email": "amina@example.com",
  "consent_status": "unknown"
}

Contact including available channel identities.

Parameters, fields and errors

Parameters

NameInRequiredDescription
contact_idpathYes integer

Response fields: Contact.

Common errors · Delivery and safe retries

PATCH/v1/integrations/contacts/{contact_id}

Partially update a contact

Omitted fields remain unchanged. Use the profile-field schema when updating profile_data.

Required scope: contacts:write

Request

Replace CONTACT_ID with your contact’s id. Replace the sample name with the value you want to save. Include only fields you intend to change.

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  --request PATCH \
  --header 'Content-Type: application/json' \
  --data-binary @- \
  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID' <<'JSON'
{
  "display_name": "Amina Updated"
}
JSON

Response

HTTP 200
{
  "id": 91,
  "display_name": "Amina Updated",
  "whatsapp_number": "+254700000001",
  "email": "amina@example.com",
  "consent_status": "unknown"
}

Updated contact.

Parameters, fields and errors

Parameters

NameInRequiredDescription
contact_idpathYes integer

Request fields: ContactInput.

Response fields: Contact.

Common errors · Delivery and safe retries

DELETE/v1/integrations/contacts/{contact_id}

Soft-delete a contact

Required scope: contacts:delete

Request

Replace CONTACT_ID with your contact’s id.

cURL
curl --fail-with-body --include \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --request DELETE \
  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID'

Response

HTTP 204
HTTP/1.1 204 No Content

Contact soft-deleted. No response body.

Parameters, fields and errors

Parameters

NameInRequiredDescription
contact_idpathYes integer

Common errors · Delivery and safe retries

GET/v1/integrations/contact_profile_fields

List contact profile-field definitions

Required scope: contacts:read

Request

cURL
curl --fail-with-body \
  --user 'YOUR_API_KEY:YOUR_API_SECRET' \
  --write-out '\nHTTP %{http_code}\n' \
  'https://stage.api.ictlife.com/v1/integrations/contact_profile_fields'

Response

HTTP 200
{
  "profile_fields": [
    {
      "id": 7,
      "field_key": "customer_tier",
      "label": "Customer tier",
      "field_type": "text",
      "is_required": false,
      "is_default": false,
      "display_order": 1
    }
  ]
}

The workspace’s custom contact schema.

Parameters, fields and errors

Response fields: ProfileFieldList.

Common errors · Delivery and safe retries

Common errors

HTTPMeaning
400Invalid request or unavailable channel; inspect the error response.
401Missing, invalid or revoked integration credentials.
403The credential does not have the required permission.
404Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message.
500A server error occurred. Reconcile message sends before retrying.

Object schemas

WhatsappChannelList

FieldTypeRequired in objectDescription
dataarray of WhatsappChannelYes

ContactUpsertResult

FieldTypeRequired in objectDescription
actionstringYesAllowed: create, update.
contactContactYes

ProfileFieldList

FieldTypeRequired in objectDescription
profile_fieldsarray of ProfileFieldNo

TemplateSend

FieldTypeRequired in objectDescription
recipientstringYesConsenting recipient in E.164 format, including + and country code.
template_idstringYesDiscovered approved template ID; language and components come from the template definition. Maximum 128 characters; server byte limits also apply.
template_variablesobjectNoTemplate-defined string values. Keys: body:1, body:name, header:1, button_1:1, button_1:payload, button_1:flow_token, header:media_id; carousel keys use card_1: prefix. Keys 1–128 characters. Omit for fixed text; use only keys required or supported by the chosen template. Up to 100 fields.
external_referencestringNoOptional private business reference, shown in Sikio; not automatically sent to WhatsApp. Maximum 256 characters; server byte limits also apply.
contextobjectNoOptional private message-level business context; keys 1–64 characters. Not a contact profile update. Up to 25 fields.

WhatsappChannel

FieldTypeRequired in objectDescription
idintegerNo
namestringNo
phone_number_idstringNoProvider ID for information; use id in Sikio request paths.

TemplateList

FieldTypeRequired in objectDescription
dataarray of objectNo
data[].idstringNo
data[].namestringNo
data[].languagestringNo
data[].statusstringNo
data[].componentsarray of objectNoOwned template definitions including headers, body, footer, buttons and supported carousel cards.
pagingobjectNo
paging.has_morebooleanNo
paging.cursorsobjectNo
paging.cursors.afterstringNo

WhatsappMessage

FieldTypeRequired in objectDescription
idintegerNoSikio integration request ID. Use for status lookup.
channel_idintegerNo
statusstringNoAllowed: processing, accepted, failed, uncertain.
conversation_idstringNo
message_idstringNo
provider_message_idstringNo
external_referencestringNo
errorstringNoFailure detail or bookkeeping reconciliation warning; do not automatically resend an accepted request.
delivery_statusstringNoMay be absent before a persisted message is available. Acceptance is not delivery; a missing receipt is not proof of failure. Allowed: unknown, delivered, read.
created_atstringNo
updated_atstringNo
delivered_atstringNo
read_atstringNo

UploadedMedia

FieldTypeRequired in objectDescription
media_idstringNo
channel_idintegerNo
mime_typestringNo
size_bytesintegerNo

ContactInput

FieldTypeRequired in objectDescription
display_namestringNoDisplay name; otherwise derived from a supplied name or identity.
first_namestring | nullNo
last_namestring | nullNo
phone_numberstring | nullNo
whatsapp_numberstring | nullNo
emailstring | nullNo
languagestring | nullNo
acquisition_sourcestring | nullNo
preferred_channelstring | nullNoAllowed: whatsapp, sms, email.
consent_statusstringNoAllowed: unknown, opted_in, opted_out.
consented_atstring | nullNo
last_seen_atstring | nullNo
profile_dataobjectNoValues matching the workspace’s profile-field definitions.

Contact

FieldTypeRequired in objectDescription
display_namestringNoDisplay name; otherwise derived from a supplied name or identity.
first_namestring | nullNo
last_namestring | nullNo
phone_numberstring | nullNo
whatsapp_numberstring | nullNo
emailstring | nullNo
languagestring | nullNo
acquisition_sourcestring | nullNo
preferred_channelstring | nullNoAllowed: whatsapp, sms, email.
consent_statusstringNoAllowed: unknown, opted_in, opted_out.
consented_atstring | nullNo
last_seen_atstring | nullNo
profile_dataobjectNoValues matching the workspace’s profile-field definitions.
idintegerNo
group_idintegerNoRead-only owning workspace; never supplied to integration endpoints.
identitiesarray of objectNo
created_atstringNo
updated_atstringNo

ContactList

FieldTypeRequired in objectDescription
contactsarray of ContactNo
paginationobjectNo
pagination.countintegerNo
pagination.pageintegerNo
pagination.perintegerNo

ProfileField

FieldTypeRequired in objectDescription
idintegerNo
field_keystringNo
labelstringNo
field_typestringNoAllowed: text, number, date, select, boolean.
optionsJSON valueNo
is_requiredbooleanNo
is_defaultbooleanNo
display_orderintegerNo
Next guideAPI documentation changelog →