{
  "openapi": "3.0.3",
  "info": {
    "title": "Sikio Integrations API",
    "version": "1.0.0",
    "description": "Organization-scoped WhatsApp template messaging and Contacts APIs. Basic authentication uses the API key as username and API secret as password. Credentials belong on your server. Per-operation x-required-scopes lists permissions granted in Sikio. Template creation/approval stays in the dashboard. No public campaign-send endpoint or outbound webhook subscription is included."
  },
  "servers": [
    {
      "url": "https://api.ictlife.com/v1",
      "description": "Production"
    },
    {
      "url": "https://stage.api.ictlife.com/v1",
      "description": "Staging — separate workspace and credentials"
    }
  ],
  "security": [
    {
      "integrationKey": []
    }
  ],
  "tags": [
    {
      "name": "WhatsApp"
    },
    {
      "name": "Contacts"
    }
  ],
  "paths": {
    "/integrations/whatsapp/channels": {
      "get": {
        "operationId": "listWhatsappChannels",
        "summary": "List connected WhatsApp channels",
        "description": "",
        "tags": [
          "WhatsApp"
        ],
        "x-required-scopes": [
          "whatsapp:read"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Eligible native WhatsApp channels in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsappChannelList"
                },
                "example": {
                  "data": [
                    {
                      "id": 42,
                      "name": "Customer service",
                      "phone_number_id": "WHATSAPP_PHONE_NUMBER_ID"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/whatsapp/channels/{channel_id}/templates": {
      "get": {
        "operationId": "listWhatsappTemplates",
        "summary": "List WhatsApp templates",
        "description": "Inspect status, components and placeholder names. Only an APPROVED template in this channel’s WhatsApp Business Account can be sent.",
        "tags": [
          "WhatsApp"
        ],
        "x-required-scopes": [
          "whatsapp:read"
        ],
        "parameters": [
          {
            "name": "channel_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor from paging.cursors.after. Continue while paging.has_more is true."
          }
        ],
        "responses": {
          "200": {
            "description": "Template definitions and cursor pagination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateList"
                },
                "example": {
                  "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
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/templates\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/whatsapp/channels/{channel_id}/messages": {
      "post": {
        "operationId": "sendWhatsappTemplate",
        "summary": "Send an approved template",
        "description": "Uses the discovered numeric Sikio channel ID. Maximum JSON body is 32 KiB. Unsupported template components, missing or surplus variables fail before provider sending.",
        "tags": [
          "WhatsApp"
        ],
        "x-required-scopes": [
          "whatsapp:send"
        ],
        "parameters": [
          {
            "name": "channel_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique per intended send. Retry with the same credential, channel, key and identical payload.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{1,128}$",
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateSend"
              },
              "example": {
                "recipient": "+254700000001",
                "template_id": "YOUR_APPROVED_TEMPLATE_ID"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Request is accepted, processing or uncertain. Inspect status; HTTP acceptance does not confirm delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsappMessage"
                },
                "example": {
                  "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"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "409": {
            "description": "This credential already used the idempotency key for a different payload or channel."
          },
          "422": {
            "description": "Preparation failed before a send or WhatsApp explicitly rejected it. The failed request retains its ID and retry key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsappMessage"
                }
              }
            }
          },
          "429": {
            "description": "New-send limit reached: 60 per minute per credential across replicas. Identical replays do not consume another slot.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  --request POST \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: UNIQUE_MESSAGE_KEY' \\\n  --data-binary @- \\\n  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages' <<'JSON'\n{\n  \"recipient\": \"+254700000001\",\n  \"template_id\": \"YOUR_APPROVED_TEMPLATE_ID\"\n}\nJSON"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages\", {\n  method: 'POST',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n    'Content-Type': 'application/json',\n    'Idempotency-Key': 'UNIQUE_MESSAGE_KEY',\n  },\n  body: JSON.stringify({\n    \"recipient\": \"+254700000001\",\n    \"template_id\": \"YOUR_APPROVED_TEMPLATE_ID\"\n  }),\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nbody = json.dumps({\n    \"recipient\": \"+254700000001\",\n    \"template_id\": \"YOUR_APPROVED_TEMPLATE_ID\"\n}).encode()\nheaders['Content-Type'] = 'application/json'\nheaders['Idempotency-Key'] = 'UNIQUE_MESSAGE_KEY'\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/messages\", method='POST', headers=headers, data=body)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/whatsapp/channels/{channel_id}/media": {
      "post": {
        "operationId": "uploadWhatsappMedia",
        "summary": "Upload an optional media asset",
        "description": "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.",
        "tags": [
          "WhatsApp"
        ],
        "x-required-scopes": [
          "whatsapp:send"
        ],
        "parameters": [
          {
            "name": "channel_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Sendable WhatsApp media ID; upload alone sends no message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedMedia"
                },
                "example": {
                  "media_id": "WHATSAPP_MEDIA_ID",
                  "channel_id": 42,
                  "mime_type": "image/png",
                  "size_bytes": 24576
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "413": {
            "description": "The file or request is too large."
          },
          "429": {
            "description": "Upload concurrency exceeded; retry after the supplied delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 2
                }
              }
            }
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          },
          "502": {
            "description": "Provider upload failed; no media ID was confirmed."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "additionalProperties": false,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  --request POST \\\n  --form 'file=@./header-image.png;type=image/png' \\\n  'https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/media'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "import { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\nconst filePath = './header-image.png';\nconst mimeType = 'image/png';\nconst file = await readFile(filePath);\nconst form = new FormData();\nform.append('file', new Blob([file], { type: mimeType }), basename(filePath));\nconst credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/media\", {\n  method: 'POST',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n  body: form,\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\nfrom pathlib import Path\nimport uuid\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nfile = Path('./header-image.png')\nmime_type = 'image/png'\nboundary = 'sikio-' + uuid.uuid4().hex\nfilename = file.name.replace('\"', '').replace('\\r', '').replace('\\n', '')\nprefix = (f'--{boundary}\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"{filename}\"\\r\\n'\n          f'Content-Type: {mime_type}\\r\\n\\r\\n').encode()\nbody = prefix + file.read_bytes() + f'\\r\\n--{boundary}--\\r\\n'.encode()\nheaders['Content-Type'] = 'multipart/form-data; boundary=' + boundary\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/channels/CHANNEL_ID/media\", method='POST', headers=headers, data=body)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/whatsapp/messages/{request_id}": {
      "get": {
        "operationId": "getWhatsappMessage",
        "summary": "Get a message request and receipts",
        "description": "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.",
        "tags": [
          "WhatsApp"
        ],
        "x-required-scopes": [
          "whatsapp:read"
        ],
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request belonging to this workspace and the original API credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsappMessage"
                },
                "example": {
                  "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"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/whatsapp/messages/REQUEST_ID\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "List and search contacts",
        "description": "",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:read"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number."
          },
          {
            "name": "per",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size; capped at 100."
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Contact search text."
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts and page-based pagination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactList"
                },
                "example": {
                  "contacts": [
                    {
                      "id": 91,
                      "display_name": "Amina Customer",
                      "whatsapp_number": "+254700000001",
                      "email": "amina@example.com",
                      "consent_status": "unknown"
                    }
                  ],
                  "pagination": {
                    "count": 1,
                    "page": 1,
                    "per": 20
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      },
      "post": {
        "operationId": "createContact",
        "summary": "Create a contact",
        "description": "Supply a name or usable identity from which a display name can be derived. Creating a contact is not consent to send a message.",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:write"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactInput"
              },
              "example": {
                "display_name": "Amina Customer",
                "whatsapp_number": "+254700000001",
                "email": "amina@example.com",
                "consent_status": "unknown"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                },
                "example": {
                  "id": 91,
                  "display_name": "Amina Customer",
                  "whatsapp_number": "+254700000001",
                  "email": "amina@example.com",
                  "consent_status": "unknown"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  --request POST \\\n  --header 'Content-Type: application/json' \\\n  --data-binary @- \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts' <<'JSON'\n{\n  \"display_name\": \"Amina Customer\",\n  \"whatsapp_number\": \"+254700000001\",\n  \"email\": \"amina@example.com\",\n  \"consent_status\": \"unknown\"\n}\nJSON"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts\", {\n  method: 'POST',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({\n    \"display_name\": \"Amina Customer\",\n    \"whatsapp_number\": \"+254700000001\",\n    \"email\": \"amina@example.com\",\n    \"consent_status\": \"unknown\"\n  }),\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nbody = json.dumps({\n    \"display_name\": \"Amina Customer\",\n    \"whatsapp_number\": \"+254700000001\",\n    \"email\": \"amina@example.com\",\n    \"consent_status\": \"unknown\"\n}).encode()\nheaders['Content-Type'] = 'application/json'\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts\", method='POST', headers=headers, data=body)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/contacts/upsert": {
      "post": {
        "operationId": "upsertContact",
        "summary": "Create or update by contact identity",
        "description": "Match by normalized phone, WhatsApp or email identity. Supply at least one matching identity. Conflicting identities are rejected instead of merging contacts automatically.",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:write"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactInput"
              },
              "example": {
                "display_name": "Amina Customer",
                "whatsapp_number": "+254700000001",
                "email": "amina@example.com",
                "consent_status": "unknown"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created or updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactUpsertResult"
                },
                "example": {
                  "action": "create",
                  "contact": {
                    "id": 91,
                    "display_name": "Amina Customer",
                    "whatsapp_number": "+254700000001",
                    "email": "amina@example.com",
                    "consent_status": "unknown"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  --request POST \\\n  --header 'Content-Type: application/json' \\\n  --data-binary @- \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts/upsert' <<'JSON'\n{\n  \"display_name\": \"Amina Customer\",\n  \"whatsapp_number\": \"+254700000001\",\n  \"email\": \"amina@example.com\",\n  \"consent_status\": \"unknown\"\n}\nJSON"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts/upsert\", {\n  method: 'POST',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({\n    \"display_name\": \"Amina Customer\",\n    \"whatsapp_number\": \"+254700000001\",\n    \"email\": \"amina@example.com\",\n    \"consent_status\": \"unknown\"\n  }),\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nbody = json.dumps({\n    \"display_name\": \"Amina Customer\",\n    \"whatsapp_number\": \"+254700000001\",\n    \"email\": \"amina@example.com\",\n    \"consent_status\": \"unknown\"\n}).encode()\nheaders['Content-Type'] = 'application/json'\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts/upsert\", method='POST', headers=headers, data=body)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/contacts/{contact_id}": {
      "get": {
        "operationId": "getContact",
        "summary": "Get a contact",
        "description": "",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:read"
        ],
        "parameters": [
          {
            "name": "contact_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact including available channel identities.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                },
                "example": {
                  "id": 91,
                  "display_name": "Amina Customer",
                  "whatsapp_number": "+254700000001",
                  "email": "amina@example.com",
                  "consent_status": "unknown"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      },
      "patch": {
        "operationId": "updateContact",
        "summary": "Partially update a contact",
        "description": "Omitted fields remain unchanged. Use the profile-field schema when updating profile_data.",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:write"
        ],
        "parameters": [
          {
            "name": "contact_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactInput"
              },
              "example": {
                "display_name": "Amina Updated"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                },
                "example": {
                  "id": 91,
                  "display_name": "Amina Updated",
                  "whatsapp_number": "+254700000001",
                  "email": "amina@example.com",
                  "consent_status": "unknown"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  --request PATCH \\\n  --header 'Content-Type: application/json' \\\n  --data-binary @- \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID' <<'JSON'\n{\n  \"display_name\": \"Amina Updated\"\n}\nJSON"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", {\n  method: 'PATCH',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({\n    \"display_name\": \"Amina Updated\"\n  }),\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nbody = json.dumps({\n    \"display_name\": \"Amina Updated\"\n}).encode()\nheaders['Content-Type'] = 'application/json'\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", method='PATCH', headers=headers, data=body)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      },
      "delete": {
        "operationId": "deleteContact",
        "summary": "Soft-delete a contact",
        "description": "",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:delete"
        ],
        "parameters": [
          {
            "name": "contact_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Contact soft-deleted. No response body."
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body --include \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --request DELETE \\\n  'https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", {\n  method: 'DELETE',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contacts/CONTACT_ID\", method='DELETE', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    },
    "/integrations/contact_profile_fields": {
      "get": {
        "operationId": "listContactProfileFields",
        "summary": "List contact profile-field definitions",
        "description": "",
        "tags": [
          "Contacts"
        ],
        "x-required-scopes": [
          "contacts:read"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The workspace’s custom contact schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileFieldList"
                },
                "example": {
                  "profile_fields": [
                    {
                      "id": 7,
                      "field_key": "customer_tier",
                      "label": "Customer tier",
                      "field_type": "text",
                      "is_required": false,
                      "is_default": false,
                      "display_order": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or unavailable channel; inspect the error response."
          },
          "401": {
            "description": "Missing, invalid or revoked integration credentials."
          },
          "403": {
            "description": "The credential does not have the required permission."
          },
          "404": {
            "description": "Resource unavailable to this credential. The platform can also mask unauthenticated requests with 404; inspect error_code and error_message."
          },
          "500": {
            "description": "A server error occurred. Reconcile message sends before retrying."
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl --fail-with-body \\\n  --user 'YOUR_API_KEY:YOUR_API_SECRET' \\\n  --write-out '\\nHTTP %{http_code}\\n' \\\n  'https://stage.api.ictlife.com/v1/integrations/contact_profile_fields'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (Node.js)",
            "source": "const credentials = Buffer.from('YOUR_API_KEY:YOUR_API_SECRET').toString('base64');\nconst response = await fetch(\"https://stage.api.ictlife.com/v1/integrations/contact_profile_fields\", {\n  method: 'GET',\n  headers: {\n    Authorization: 'Basic ' + credentials,\n  },\n});\nconst text = await response.text();\nif (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);\nconsole.log(response.status, text ? JSON.parse(text) : null);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import base64, json\nimport urllib.request, urllib.error\ncredentials = base64.b64encode(b'YOUR_API_KEY:YOUR_API_SECRET').decode()\nheaders = {'Authorization': 'Basic ' + credentials}\nrequest = urllib.request.Request(\"https://stage.api.ictlife.com/v1/integrations/contact_profile_fields\", method='GET', headers=headers)\ntry:\n    with urllib.request.urlopen(request, timeout=30) as response:\n        text = response.read().decode()\n        print(response.status, json.loads(text) if text else None)\nexcept urllib.error.HTTPError as error:\n    raise SystemExit(f'HTTP {error.code}: {error.read().decode()}')"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "integrationKey": {
        "type": "http",
        "scheme": "basic",
        "description": "API key as username; API secret as password. HTTPS only. The credential determines the workspace."
      }
    },
    "schemas": {
      "WhatsappChannelList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WhatsappChannel"
            }
          }
        },
        "example": {
          "data": [
            {
              "id": 42,
              "name": "Customer service",
              "phone_number_id": "WHATSAPP_PHONE_NUMBER_ID"
            }
          ]
        }
      },
      "ContactUpsertResult": {
        "type": "object",
        "required": [
          "action",
          "contact"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "create",
              "update"
            ]
          },
          "contact": {
            "$ref": "#/components/schemas/Contact"
          }
        },
        "example": {
          "action": "create",
          "contact": {
            "id": 91,
            "display_name": "Amina Customer"
          }
        }
      },
      "ProfileFieldList": {
        "type": "object",
        "properties": {
          "profile_fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileField"
            }
          }
        },
        "example": {
          "profile_fields": [
            {
              "id": 7,
              "field_key": "customer_tier",
              "label": "Customer tier",
              "field_type": "text",
              "is_required": false,
              "is_default": false,
              "display_order": 1
            }
          ]
        }
      },
      "TemplateSend": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "recipient",
          "template_id"
        ],
        "properties": {
          "recipient": {
            "type": "string",
            "pattern": "^\\+[1-9][0-9]{7,14}$",
            "description": "Consenting recipient in E.164 format, including + and country code."
          },
          "template_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Discovered approved template ID; language and components come from the template definition."
          },
          "template_variables": {
            "type": "object",
            "maxProperties": 100,
            "additionalProperties": {
              "type": "string",
              "maxLength": 4096
            },
            "description": "Template-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."
          },
          "external_reference": {
            "type": "string",
            "maxLength": 256,
            "description": "Optional private business reference, shown in Sikio; not automatically sent to WhatsApp."
          },
          "context": {
            "type": "object",
            "maxProperties": 25,
            "additionalProperties": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Optional private message-level business context; keys 1–64 characters. Not a contact profile update."
          }
        },
        "example": {
          "recipient": "+254700000001",
          "template_id": "YOUR_APPROVED_TEMPLATE_ID",
          "template_variables": {
            "body:1": "Amina",
            "body:2": "ORDER-1042"
          },
          "external_reference": "ORDER-1042",
          "context": {
            "order_reference": "ORDER-1042"
          }
        }
      },
      "WhatsappChannel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 42
          },
          "name": {
            "type": "string",
            "example": "Customer service"
          },
          "phone_number_id": {
            "type": "string",
            "description": "Provider ID for information; use id in Sikio request paths."
          }
        }
      },
      "TemplateList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "language": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "example": "APPROVED"
                },
                "components": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "description": "Owned template definitions including headers, body, footer, buttons and supported carousel cards."
                }
              }
            }
          },
          "paging": {
            "type": "object",
            "properties": {
              "has_more": {
                "type": "boolean"
              },
              "cursors": {
                "type": "object",
                "properties": {
                  "after": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "WhatsappMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Sikio integration request ID. Use for status lookup."
          },
          "channel_id": {
            "type": "integer",
            "format": "int64"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "accepted",
              "failed",
              "uncertain"
            ]
          },
          "conversation_id": {
            "type": "string"
          },
          "message_id": {
            "type": "string"
          },
          "provider_message_id": {
            "type": "string"
          },
          "external_reference": {
            "type": "string"
          },
          "error": {
            "type": "string",
            "description": "Failure detail or bookkeeping reconciliation warning; do not automatically resend an accepted request."
          },
          "delivery_status": {
            "type": "string",
            "enum": [
              "unknown",
              "delivered",
              "read"
            ],
            "description": "May be absent before a persisted message is available. Acceptance is not delivery; a missing receipt is not proof of failure."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "example": {
          "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"
        }
      },
      "UploadedMedia": {
        "type": "object",
        "properties": {
          "media_id": {
            "type": "string"
          },
          "channel_id": {
            "type": "integer"
          },
          "mime_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          }
        },
        "example": {
          "media_id": "WHATSAPP_MEDIA_ID",
          "channel_id": 42,
          "mime_type": "image/jpeg",
          "size_bytes": 24576
        }
      },
      "ContactInput": {
        "type": "object",
        "properties": {
          "display_name": {
            "type": "string",
            "description": "Display name; otherwise derived from a supplied name or identity."
          },
          "first_name": {
            "type": "string",
            "nullable": true
          },
          "last_name": {
            "type": "string",
            "nullable": true
          },
          "phone_number": {
            "type": "string",
            "nullable": true
          },
          "whatsapp_number": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "acquisition_source": {
            "type": "string",
            "nullable": true
          },
          "preferred_channel": {
            "type": "string",
            "nullable": true,
            "enum": [
              "whatsapp",
              "sms",
              "email",
              null
            ]
          },
          "consent_status": {
            "type": "string",
            "enum": [
              "unknown",
              "opted_in",
              "opted_out"
            ]
          },
          "consented_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "profile_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Values matching the workspace’s profile-field definitions."
          }
        },
        "example": {
          "display_name": "Amina Customer",
          "whatsapp_number": "+254700000001",
          "email": "amina@example.com",
          "consent_status": "unknown"
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "display_name": {
            "type": "string",
            "description": "Display name; otherwise derived from a supplied name or identity."
          },
          "first_name": {
            "type": "string",
            "nullable": true
          },
          "last_name": {
            "type": "string",
            "nullable": true
          },
          "phone_number": {
            "type": "string",
            "nullable": true
          },
          "whatsapp_number": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "acquisition_source": {
            "type": "string",
            "nullable": true
          },
          "preferred_channel": {
            "type": "string",
            "nullable": true,
            "enum": [
              "whatsapp",
              "sms",
              "email",
              null
            ]
          },
          "consent_status": {
            "type": "string",
            "enum": [
              "unknown",
              "opted_in",
              "opted_out"
            ]
          },
          "consented_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "profile_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Values matching the workspace’s profile-field definitions."
          },
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "group_id": {
            "type": "integer",
            "description": "Read-only owning workspace; never supplied to integration endpoints."
          },
          "identities": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContactList": {
        "type": "object",
        "properties": {
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "page": {
                "type": "integer"
              },
              "per": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ProfileField": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "field_key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "field_type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "date",
              "select",
              "boolean"
            ]
          },
          "options": {},
          "is_required": {
            "type": "boolean"
          },
          "is_default": {
            "type": "boolean"
          },
          "display_order": {
            "type": "integer"
          }
        }
      }
    }
  }
}
