{
  "info": {
    "_postman_id": "2bf3dbb0-aab3-4f6e-a270-bf5c366a125c",
    "name": "Selanim Bulk SMS API v1.0.0",
    "description": "The Selanim Bulk SMS API sends messages, reports what happened to them, and\ntells you what it cost.\n\nEvery request is authenticated with an API key scoped to one account. Keys\nare created in the portal under **Developer API** and come in two flavours:\n\n| Prefix       | Behaviour                                                       |\n|--------------|-----------------------------------------------------------------|\n| `sbs_live_`  | Sends real messages and debits your wallet.                     |\n| `sbs_test_`  | Validates the request and simulates delivery. Nothing is billed.|\n\nStart with a test key. It exercises the whole path - validation, sender ID\nchecks, delivery webhooks - without spending anything.\n\n## Before you send\n\n1. Open **Variables** on this collection and paste your key into `apiKey`. Start with a `sbs_test_` key — it exercises the whole path without spending credits.\n\n2. `baseUrl` defaults to production. Point it at `http://localhost:8081/v1` to run against a local stack.\n\n3. Every request inherits Bearer auth from the collection, so nothing needs an `Authorization` header of its own.\n\nGenerated from openapi.yaml. Do not edit by hand — the contract lives at https://docs.selanimsms.co.tz.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.selanimsms.co.tz/v1",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "messageId",
      "value": "",
      "type": "string"
    },
    {
      "key": "contactId",
      "value": "",
      "type": "string"
    },
    {
      "key": "groupId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Messages",
      "description": "Sending, and finding out what happened afterwards.",
      "item": [
        {
          "name": "Send a message — One message, two recipients",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const data = pm.response.json();",
                  "const value = data.messages?.[0]?.id;",
                  "",
                  "if (value) {",
                  "    pm.collectionVariables.set(\"messageId\", value);",
                  "    console.log(\"messageId =\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"to\": [\n    \"255712345678\",\n    \"0754112233\"\n  ],\n  \"message\": \"Dear parent, Term 3 fees are due on 25th Aug.\",\n  \"reference\": \"term3-fees\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/sms/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms",
                "send"
              ]
            },
            "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "202 Accepted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"to\": [\n    \"255712345678\",\n    \"0754112233\"\n  ],\n  \"message\": \"Dear parent, Term 3 fees are due on 25th Aug.\",\n  \"reference\": \"term3-fees\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/sms/send",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sms",
                    "send"
                  ]
                },
                "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"requestId\": \"REQ-90022\",\n  \"parts\": 1,\n  \"cost\": 30,\n  \"currency\": \"TZS\",\n  \"reference\": \"term3-fees\",\n  \"messages\": [\n    {\n      \"id\": \"SM-4a91c0\",\n      \"to\": \"+255712345678\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    },\n    {\n      \"id\": \"SM-4a91c1\",\n      \"to\": \"+255754112233\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Send a message — A different body per recipient",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const data = pm.response.json();",
                  "const value = data.messages?.[0]?.id;",
                  "",
                  "if (value) {",
                  "    pm.collectionVariables.set(\"messageId\", value);",
                  "    console.log(\"messageId =\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"message\": \"Hello {{name}}, your balance is {{amount}}.\",\n  \"to\": [\n    {\n      \"phone\": \"255712345678\",\n      \"variables\": {\n        \"name\": \"Rehema\",\n        \"amount\": \"TZS 45,000\"\n      }\n    },\n    {\n      \"phone\": \"255754112233\",\n      \"variables\": {\n        \"name\": \"Baraka\",\n        \"amount\": \"TZS 12,500\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/sms/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms",
                "send"
              ]
            },
            "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "202 Accepted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"message\": \"Hello {{name}}, your balance is {{amount}}.\",\n  \"to\": [\n    {\n      \"phone\": \"255712345678\",\n      \"variables\": {\n        \"name\": \"Rehema\",\n        \"amount\": \"TZS 45,000\"\n      }\n    },\n    {\n      \"phone\": \"255754112233\",\n      \"variables\": {\n        \"name\": \"Baraka\",\n        \"amount\": \"TZS 12,500\"\n      }\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/sms/send",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sms",
                    "send"
                  ]
                },
                "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"requestId\": \"REQ-90022\",\n  \"parts\": 1,\n  \"cost\": 30,\n  \"currency\": \"TZS\",\n  \"reference\": \"term3-fees\",\n  \"messages\": [\n    {\n      \"id\": \"SM-4a91c0\",\n      \"to\": \"+255712345678\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    },\n    {\n      \"id\": \"SM-4a91c1\",\n      \"to\": \"+255754112233\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Send a message — Send later",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const data = pm.response.json();",
                  "const value = data.messages?.[0]?.id;",
                  "",
                  "if (value) {",
                  "    pm.collectionVariables.set(\"messageId\", value);",
                  "    console.log(\"messageId =\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"to\": [\n    \"255712345678\"\n  ],\n  \"message\": \"Your appointment is tomorrow at 10:00.\",\n  \"scheduleAt\": \"2026-09-01T07:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/sms/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms",
                "send"
              ]
            },
            "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "202 Accepted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"senderId\": \"SELANIM\",\n  \"to\": [\n    \"255712345678\"\n  ],\n  \"message\": \"Your appointment is tomorrow at 10:00.\",\n  \"scheduleAt\": \"2026-09-01T07:00:00Z\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/sms/send",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sms",
                    "send"
                  ]
                },
                "description": "Queues one message per recipient and returns immediately. Delivery is\nasynchronous - subscribe to `message.delivered` and `message.failed`\nwebhooks, or poll `GET /sms/{id}`.\n\nBilling is per **segment per recipient**, not per request. A 200\ncharacter message to two people costs four segments. If your balance\ncannot cover the whole batch, nothing is sent and the call returns\n`insufficient_funds` - there is no partial send.\n\n`POST /sms/send` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"requestId\": \"REQ-90022\",\n  \"parts\": 1,\n  \"cost\": 30,\n  \"currency\": \"TZS\",\n  \"reference\": \"term3-fees\",\n  \"messages\": [\n    {\n      \"id\": \"SM-4a91c0\",\n      \"to\": \"+255712345678\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    },\n    {\n      \"id\": \"SM-4a91c1\",\n      \"to\": \"+255754112233\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "List messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sms",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "queued",
                  "disabled": true
                },
                {
                  "key": "reference",
                  "value": "",
                  "description": "Filter to the messages you tagged with this reference.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "",
                  "description": "ISO 8601 timestamp. Messages created at or after this.",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "",
                  "description": "ISO 8601 timestamp. Messages created at or before this.",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "pageSize",
                  "value": "25",
                  "disabled": true
                }
              ]
            },
            "description": "Your message log, newest first.\n\n`GET /sms` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/sms",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sms"
                  ],
                  "query": [
                    {
                      "key": "status",
                      "value": "queued",
                      "disabled": true
                    },
                    {
                      "key": "reference",
                      "value": "",
                      "description": "Filter to the messages you tagged with this reference.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "",
                      "description": "ISO 8601 timestamp. Messages created at or after this.",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "",
                      "description": "ISO 8601 timestamp. Messages created at or before this.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "25",
                      "disabled": true
                    }
                  ]
                },
                "description": "Your message log, newest first.\n\n`GET /sms` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"id\": \"SM-4a91c0\",\n      \"to\": \"+255712345678\",\n      \"senderId\": \"SELANIM\",\n      \"message\": \"\",\n      \"network\": \"Vodacom\",\n      \"status\": \"queued\",\n      \"parts\": 0,\n      \"cost\": 0,\n      \"reference\": \"\",\n      \"failureReason\": \"\",\n      \"createdAt\": \"2026-01-01T09:00:00Z\",\n      \"deliveredAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ],\n  \"meta\": {\n    \"page\": 0,\n    \"pageSize\": 0,\n    \"total\": 0,\n    \"totalPages\": 0\n  }\n}"
            }
          ]
        },
        {
          "name": "Fetch one message",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sms/:messageId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms",
                ":messageId"
              ],
              "variable": [
                {
                  "key": "messageId",
                  "value": "{{messageId}}"
                }
              ]
            },
            "description": "Fetch one message\n\n`GET /sms/{messageId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/sms/:messageId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sms",
                    ":messageId"
                  ],
                  "variable": [
                    {
                      "key": "messageId",
                      "value": "{{messageId}}"
                    }
                  ]
                },
                "description": "Fetch one message\n\n`GET /sms/{messageId}` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"id\": \"SM-4a91c0\",\n  \"to\": \"+255712345678\",\n  \"senderId\": \"SELANIM\",\n  \"message\": \"\",\n  \"network\": \"Vodacom\",\n  \"status\": \"queued\",\n  \"parts\": 0,\n  \"cost\": 0,\n  \"reference\": \"\",\n  \"failureReason\": \"\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\",\n  \"deliveredAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "WhatsApp",
      "description": "Two-way WhatsApp: sending, and pulling the messages your customers sent you. Meta itself has no pull API — it pushes to a webhook and keeps nothing — so GET /whatsapp/messages serves our stored copy, which is what makes it pollable, re-readable and resumable after an outage.",
      "item": [
        {
          "name": "Pull messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/whatsapp/messages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "whatsapp",
                "messages"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "The `nextCursor` from your previous call. Opaque: send it back verbatim and do not parse it. Omit to start from the newest message.",
                  "disabled": true
                },
                {
                  "key": "direction",
                  "value": "inbound",
                  "description": "`inbound` (the default) is what customers sent you, `outbound` is what you sent, `all` returns both in one stream.",
                  "disabled": true
                },
                {
                  "key": "since",
                  "value": "",
                  "description": "RFC3339 timestamp. Only messages at or after this.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "disabled": true
                }
              ]
            },
            "description": "The inbound feed. Call it with no cursor, process `data`, then keep calling with `nextCursor` while `hasMore` is true. Storing that cursor is your entire replication state.\n\nPaging is by cursor rather than page number on purpose. An inbox gains rows at the top constantly, so with an offset your page 2 shifts between polls — you would see some messages twice and silently miss others. A cursor names a position in the data: repeating a request returns the same rows, and resuming after a two-day outage picks up exactly where you stopped.\n\n`GET /whatsapp/messages` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/whatsapp/messages",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "whatsapp",
                    "messages"
                  ],
                  "query": [
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "The `nextCursor` from your previous call. Opaque: send it back verbatim and do not parse it. Omit to start from the newest message.",
                      "disabled": true
                    },
                    {
                      "key": "direction",
                      "value": "inbound",
                      "description": "`inbound` (the default) is what customers sent you, `outbound` is what you sent, `all` returns both in one stream.",
                      "disabled": true
                    },
                    {
                      "key": "since",
                      "value": "",
                      "description": "RFC3339 timestamp. Only messages at or after this.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "50",
                      "disabled": true
                    }
                  ]
                },
                "description": "The inbound feed. Call it with no cursor, process `data`, then keep calling with `nextCursor` while `hasMore` is true. Storing that cursor is your entire replication state.\n\nPaging is by cursor rather than page number on purpose. An inbox gains rows at the top constantly, so with an offset your page 2 shifts between polls — you would see some messages twice and silently miss others. A cursor names a position in the data: repeating a request returns the same rows, and resuming after a two-day outage picks up exactly where you stopped.\n\n`GET /whatsapp/messages` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"id\": \"\",\n      \"conversationId\": \"\",\n      \"whatsappId\": \"\",\n      \"direction\": \"inbound\",\n      \"from\": \"\",\n      \"to\": \"\",\n      \"type\": \"\",\n      \"body\": \"\",\n      \"mediaId\": \"\",\n      \"mediaType\": \"\",\n      \"template\": \"\",\n      \"status\": \"queued\",\n      \"deliveryStatus\": \"sent\",\n      \"failureReason\": \"\",\n      \"cost\": 0,\n      \"reference\": \"\",\n      \"createdAt\": \"2026-01-01T09:00:00Z\",\n      \"sentAt\": \"2026-01-01T09:00:00Z\",\n      \"receivedAt\": \"2026-01-01T09:00:00Z\",\n      \"deliveredAt\": \"2026-01-01T09:00:00Z\",\n      \"readAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ],\n  \"nextCursor\": \"\",\n  \"hasMore\": true\n}"
            }
          ]
        },
        {
          "name": "Send a WhatsApp message",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": \"+255712345678\",\n  \"text\": \"\",\n  \"template\": \"order_ready\",\n  \"templateLanguage\": \"sw\",\n  \"templateParams\": [\n    \"\"\n  ],\n  \"mediaUrl\": \"https://example.co.tz/hooks/sbs\",\n  \"mediaType\": \"image\",\n  \"mediaCaption\": \"\",\n  \"reference\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/whatsapp/messages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "whatsapp",
                "messages"
              ]
            },
            "description": "Sends one message. Give `text` to reply inside an open 24-hour window, or `template` to reach somebody outside one. Answers 202 — accepted and queued, not yet delivered.\n\n`POST /whatsapp/messages` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "202 Accepted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"to\": \"+255712345678\",\n  \"text\": \"\",\n  \"template\": \"order_ready\",\n  \"templateLanguage\": \"sw\",\n  \"templateParams\": [\n    \"\"\n  ],\n  \"mediaUrl\": \"https://example.co.tz/hooks/sbs\",\n  \"mediaType\": \"image\",\n  \"mediaCaption\": \"\",\n  \"reference\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/whatsapp/messages",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "whatsapp",
                    "messages"
                  ]
                },
                "description": "Sends one message. Give `text` to reply inside an open 24-hour window, or `template` to reach somebody outside one. Answers 202 — accepted and queued, not yet delivered.\n\n`POST /whatsapp/messages` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"id\": \"\",\n  \"to\": \"\",\n  \"status\": \"\",\n  \"cost\": 0,\n  \"reference\": \"\"\n}"
            }
          ]
        },
        {
          "name": "Fetch one WhatsApp message",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/whatsapp/messages/:messageId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "whatsapp",
                "messages",
                ":messageId"
              ],
              "variable": [
                {
                  "key": "messageId",
                  "value": "{{messageId}}"
                }
              ]
            },
            "description": "Fetch one WhatsApp message\n\n`GET /whatsapp/messages/{messageId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/whatsapp/messages/:messageId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "whatsapp",
                    "messages",
                    ":messageId"
                  ],
                  "variable": [
                    {
                      "key": "messageId",
                      "value": "{{messageId}}"
                    }
                  ]
                },
                "description": "Fetch one WhatsApp message\n\n`GET /whatsapp/messages/{messageId}` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"id\": \"\",\n  \"conversationId\": \"\",\n  \"whatsappId\": \"\",\n  \"direction\": \"inbound\",\n  \"from\": \"\",\n  \"to\": \"\",\n  \"type\": \"\",\n  \"body\": \"\",\n  \"mediaId\": \"\",\n  \"mediaType\": \"\",\n  \"template\": \"\",\n  \"status\": \"queued\",\n  \"deliveryStatus\": \"sent\",\n  \"failureReason\": \"\",\n  \"cost\": 0,\n  \"reference\": \"\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\",\n  \"sentAt\": \"2026-01-01T09:00:00Z\",\n  \"receivedAt\": \"2026-01-01T09:00:00Z\",\n  \"deliveredAt\": \"2026-01-01T09:00:00Z\",\n  \"readAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "List conversations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/whatsapp/conversations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "whatsapp",
                "conversations"
              ],
              "query": [
                {
                  "key": "state",
                  "value": "open",
                  "disabled": true
                },
                {
                  "key": "unread",
                  "value": "",
                  "description": "Only threads with unread messages.",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "pageSize",
                  "value": "25",
                  "disabled": true
                }
              ]
            },
            "description": "One thread per customer. `windowOpen` and `windowRemainingSeconds` are the fields to build on — they tell you whether you may reply freely or must use an approved template, which is the rule most likely to make a first integration fail in a way that is hard to explain.\n\n`GET /whatsapp/conversations` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/whatsapp/conversations",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "whatsapp",
                    "conversations"
                  ],
                  "query": [
                    {
                      "key": "state",
                      "value": "open",
                      "disabled": true
                    },
                    {
                      "key": "unread",
                      "value": "",
                      "description": "Only threads with unread messages.",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "25",
                      "disabled": true
                    }
                  ]
                },
                "description": "One thread per customer. `windowOpen` and `windowRemainingSeconds` are the fields to build on — they tell you whether you may reply freely or must use an approved template, which is the rule most likely to make a first integration fail in a way that is hard to explain.\n\n`GET /whatsapp/conversations` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"id\": \"\",\n      \"contact\": \"+255712345678\",\n      \"contactName\": \"\",\n      \"lastMessage\": \"\",\n      \"lastMessageAt\": \"2026-01-01T09:00:00Z\",\n      \"unreadCount\": 0,\n      \"state\": \"open\",\n      \"windowOpen\": true,\n      \"windowRemainingSeconds\": 0\n    }\n  ],\n  \"meta\": {\n    \"page\": 0,\n    \"pageSize\": 0,\n    \"total\": 0,\n    \"totalPages\": 0\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Wallet",
      "description": "Balance and the ledger behind it.",
      "item": [
        {
          "name": "Current balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/wallet/balance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "wallet",
                "balance"
              ]
            },
            "description": "Balance is held in SMS credits, not currency. One credit pays for one\nsegment to one recipient.\n\n`GET /wallet/balance` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/wallet/balance",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "wallet",
                    "balance"
                  ]
                },
                "description": "Balance is held in SMS credits, not currency. One credit pays for one\nsegment to one recipient.\n\n`GET /wallet/balance` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"balance\": 12480,\n  \"currency\": \"TZS\",\n  \"lowBalanceThreshold\": 500\n}"
            }
          ]
        },
        {
          "name": "Ledger entries",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/wallet/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "wallet",
                "transactions"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "topup",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "pageSize",
                  "value": "25",
                  "disabled": true
                }
              ]
            },
            "description": "Append-only. Every credit and every debit, in the order it happened.\n\n`GET /wallet/transactions` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/wallet/transactions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "wallet",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "type",
                      "value": "topup",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "25",
                      "disabled": true
                    }
                  ]
                },
                "description": "Append-only. Every credit and every debit, in the order it happened.\n\n`GET /wallet/transactions` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"type\": \"topup\",\n      \"amount\": 0,\n      \"balanceAfter\": 0,\n      \"description\": \"\",\n      \"reference\": \"\",\n      \"createdAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ],\n  \"meta\": {\n    \"page\": 0,\n    \"pageSize\": 0,\n    \"total\": 0,\n    \"totalPages\": 0\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Sender IDs",
      "description": "The names your recipients see.",
      "item": [
        {
          "name": "List sender IDs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sender-ids",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sender-ids"
              ]
            },
            "description": "Your own sender IDs plus the shared platform names every account may\nsend under. Only `approved` ones can be used on `POST /sms/send`.\n\n`GET /sender-ids` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/sender-ids",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sender-ids"
                  ]
                },
                "description": "Your own sender IDs plus the shared platform names every account may\nsend under. Only `approved` ones can be used on `POST /sms/send`.\n\n`GET /sender-ids` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"name\": \"SELANIM\",\n      \"status\": \"pending\",\n      \"shared\": true,\n      \"purpose\": \"\",\n      \"rejectionReason\": \"\",\n      \"createdAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Request a sender ID",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"SHULEYETU\",\n  \"purpose\": \"Fee reminders and results notifications for parents.\",\n  \"sampleMessage\": \"Dear parent, Term 3 results are out.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/sender-ids",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sender-ids"
              ]
            },
            "description": "Creates a request in `pending`. Approval is manual and involves the\noperator, so allow a few working days. Keep sending under an approved\nname in the meantime.\n\n`POST /sender-ids` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "201 Created",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"SHULEYETU\",\n  \"purpose\": \"Fee reminders and results notifications for parents.\",\n  \"sampleMessage\": \"Dear parent, Term 3 results are out.\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/sender-ids",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "sender-ids"
                  ]
                },
                "description": "Creates a request in `pending`. Approval is manual and involves the\noperator, so allow a few working days. Keep sending under an approved\nname in the meantime.\n\n`POST /sender-ids` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"name\": \"SELANIM\",\n  \"status\": \"pending\",\n  \"shared\": true,\n  \"purpose\": \"\",\n  \"rejectionReason\": \"\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Contacts",
      "description": "The address book you send to.",
      "item": [
        {
          "name": "List contacts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/contacts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "description": "Matches name, phone or email.",
                  "disabled": true
                },
                {
                  "key": "groupId",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "subscribed",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "pageSize",
                  "value": "25",
                  "disabled": true
                }
              ]
            },
            "description": "List contacts\n\n`GET /contacts` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/contacts",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "contacts"
                  ],
                  "query": [
                    {
                      "key": "search",
                      "value": "",
                      "description": "Matches name, phone or email.",
                      "disabled": true
                    },
                    {
                      "key": "groupId",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "subscribed",
                      "value": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "25",
                      "disabled": true
                    }
                  ]
                },
                "description": "List contacts\n\n`GET /contacts` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"phone\": \"0713111321\",\n      \"firstName\": \"\",\n      \"lastName\": \"\",\n      \"email\": \"rehema@example.co.tz\",\n      \"notes\": \"\",\n      \"customFields\": {},\n      \"groupIds\": [],\n      \"network\": \"Vodacom\",\n      \"isSubscribed\": true,\n      \"source\": \"manual\",\n      \"createdAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ],\n  \"meta\": {\n    \"page\": 0,\n    \"pageSize\": 0,\n    \"total\": 0,\n    \"totalPages\": 0\n  }\n}"
            }
          ]
        },
        {
          "name": "Create a contact",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const data = pm.response.json();",
                  "const value = data.id;",
                  "",
                  "if (value) {",
                  "    pm.collectionVariables.set(\"contactId\", value);",
                  "    console.log(\"contactId =\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"Rehema\",\n  \"lastName\": \"Mwakalinga\",\n  \"customFields\": {\n    \"class\": \"Form 4\",\n    \"balance\": \"45000\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/contacts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts"
              ]
            },
            "description": "Create a contact\n\n`POST /contacts` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "201 Created",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"Rehema\",\n  \"lastName\": \"Mwakalinga\",\n  \"customFields\": {\n    \"class\": \"Form 4\",\n    \"balance\": \"45000\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/contacts",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "contacts"
                  ]
                },
                "description": "Create a contact\n\n`POST /contacts` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"\",\n  \"lastName\": \"\",\n  \"email\": \"rehema@example.co.tz\",\n  \"notes\": \"\",\n  \"customFields\": {},\n  \"groupIds\": [],\n  \"network\": \"Vodacom\",\n  \"isSubscribed\": true,\n  \"source\": \"manual\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "Fetch one contact",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/contacts/:contactId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts",
                ":contactId"
              ],
              "variable": [
                {
                  "key": "contactId",
                  "value": "{{contactId}}"
                }
              ]
            },
            "description": "Fetch one contact\n\n`GET /contacts/{contactId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/contacts/:contactId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "contacts",
                    ":contactId"
                  ],
                  "variable": [
                    {
                      "key": "contactId",
                      "value": "{{contactId}}"
                    }
                  ]
                },
                "description": "Fetch one contact\n\n`GET /contacts/{contactId}` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"\",\n  \"lastName\": \"\",\n  \"email\": \"rehema@example.co.tz\",\n  \"notes\": \"\",\n  \"customFields\": {},\n  \"groupIds\": [],\n  \"network\": \"Vodacom\",\n  \"isSubscribed\": true,\n  \"source\": \"manual\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "Update a contact",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"\",\n  \"lastName\": \"\",\n  \"email\": \"rehema@example.co.tz\",\n  \"notes\": \"\",\n  \"customFields\": {},\n  \"groupIds\": []\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/contacts/:contactId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts",
                ":contactId"
              ],
              "variable": [
                {
                  "key": "contactId",
                  "value": "{{contactId}}"
                }
              ]
            },
            "description": "Update a contact\n\n`PUT /contacts/{contactId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"\",\n  \"lastName\": \"\",\n  \"email\": \"rehema@example.co.tz\",\n  \"notes\": \"\",\n  \"customFields\": {},\n  \"groupIds\": []\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/contacts/:contactId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "contacts",
                    ":contactId"
                  ],
                  "variable": [
                    {
                      "key": "contactId",
                      "value": "{{contactId}}"
                    }
                  ]
                },
                "description": "Update a contact\n\n`PUT /contacts/{contactId}` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"phone\": \"0713111321\",\n  \"firstName\": \"\",\n  \"lastName\": \"\",\n  \"email\": \"rehema@example.co.tz\",\n  \"notes\": \"\",\n  \"customFields\": {},\n  \"groupIds\": [],\n  \"network\": \"Vodacom\",\n  \"isSubscribed\": true,\n  \"source\": \"manual\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "Delete a contact",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/contacts/:contactId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts",
                ":contactId"
              ],
              "variable": [
                {
                  "key": "contactId",
                  "value": "{{contactId}}"
                }
              ]
            },
            "description": "Delete a contact\n\n`DELETE /contacts/{contactId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": []
        },
        {
          "name": "Import contacts in bulk",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"contacts\": [\n    {\n      \"phone\": \"0713111321\",\n      \"firstName\": \"\",\n      \"lastName\": \"\",\n      \"email\": \"rehema@example.co.tz\",\n      \"notes\": \"\",\n      \"customFields\": {},\n      \"groupIds\": []\n    }\n  ],\n  \"skipDuplicates\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/contacts/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "contacts",
                "import"
              ]
            },
            "description": "Up to 50,000 contacts per call. Numbers are normalised before the\nduplicate check, so `0713111321` and `+255 713 111 321` collide.\n\nThe response reports every rejected row with a reason rather than\nfailing the whole batch - a spreadsheet with four bad numbers in five\nthousand should still import.\n\n`POST /contacts/import` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"contacts\": [\n    {\n      \"phone\": \"0713111321\",\n      \"firstName\": \"\",\n      \"lastName\": \"\",\n      \"email\": \"rehema@example.co.tz\",\n      \"notes\": \"\",\n      \"customFields\": {},\n      \"groupIds\": []\n    }\n  ],\n  \"skipDuplicates\": true\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/contacts/import",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "contacts",
                    "import"
                  ]
                },
                "description": "Up to 50,000 contacts per call. Numbers are normalised before the\nduplicate check, so `0713111321` and `+255 713 111 321` collide.\n\nThe response reports every rejected row with a reason rather than\nfailing the whole batch - a spreadsheet with four bad numbers in five\nthousand should still import.\n\n`POST /contacts/import` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"imported\": 4821,\n  \"updated\": 0,\n  \"skipped\": 175,\n  \"rejected\": [\n    {\n      \"row\": 12,\n      \"phone\": \"07131\",\n      \"reason\": \"invalid_phone_number\"\n    }\n  ]\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Groups",
      "description": "Audiences built from contacts.",
      "item": [
        {
          "name": "List groups",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "groups"
              ]
            },
            "description": "List groups\n\n`GET /groups` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "groups"
                  ]
                },
                "description": "List groups\n\n`GET /groups` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"data\": [\n    {\n      \"name\": \"\",\n      \"description\": \"\",\n      \"contactCount\": 0,\n      \"createdAt\": \"2026-01-01T09:00:00Z\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Create a group",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const data = pm.response.json();",
                  "const value = data.id;",
                  "",
                  "if (value) {",
                  "    pm.collectionVariables.set(\"groupId\", value);",
                  "    console.log(\"groupId =\", value);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "order-4471-confirmation",
                "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                "disabled": true
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Form 4 parents\",\n  \"description\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "groups"
              ]
            },
            "description": "Create a group\n\n`POST /groups` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "201 Created",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "order-4471-confirmation",
                    "description": "Replay protection. Send the same key with the same body within 24 hours\nand you get the original response instead of a second send - the safe\nway to retry after a timeout. The same key with a *different* body is a\n`409`.",
                    "disabled": true
                  },
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"name\": \"Form 4 parents\",\n  \"description\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "groups"
                  ]
                },
                "description": "Create a group\n\n`POST /groups` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"name\": \"\",\n  \"description\": \"\",\n  \"contactCount\": 0,\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "Delete a group",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/groups/:groupId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "groups",
                ":groupId"
              ],
              "variable": [
                {
                  "key": "groupId",
                  "value": "{{groupId}}"
                }
              ]
            },
            "description": "Deletes the group. The contacts in it are not deleted.\n\n`DELETE /groups/{groupId}` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": []
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Delivery events pushed to your endpoint.",
      "item": [
        {
          "name": "Read your webhook endpoint",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "Read your webhook endpoint\n\n`GET /webhooks` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "webhooks"
                  ]
                },
                "description": "Read your webhook endpoint\n\n`GET /webhooks` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"url\": \"https://example.co.tz/hooks/sbs\",\n  \"events\": [\n    \"message.sent\"\n  ],\n  \"enabled\": true,\n  \"secret\": \"\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        },
        {
          "name": "Set your webhook endpoint",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://brightschool.co.tz/hooks/sbs-delivery\",\n  \"events\": [\n    \"message.delivered\",\n    \"message.failed\"\n  ],\n  \"enabled\": true,\n  \"rotateSecret\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "Creates the endpoint on first call and replaces it afterwards. The\nsigning secret is returned **once**, on the call that creates it.\n\nVerify every request: recompute\n`HMAC-SHA256(secret, \"{timestamp}.{rawBody}\")` where the timestamp is the\n`X-SBS-Timestamp` header, and compare with `X-SBS-Signature` in constant\ntime. Reject anything whose timestamp is more than five minutes old.\n\nLost the secret? Send `rotateSecret: true` — there is no endpoint that\ncan read it back.\n\n`PUT /webhooks` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://brightschool.co.tz/hooks/sbs-delivery\",\n  \"events\": [\n    \"message.delivered\",\n    \"message.failed\"\n  ],\n  \"enabled\": true,\n  \"rotateSecret\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "webhooks"
                  ]
                },
                "description": "Creates the endpoint on first call and replaces it afterwards. The\nsigning secret is returned **once**, on the call that creates it.\n\nVerify every request: recompute\n`HMAC-SHA256(secret, \"{timestamp}.{rawBody}\")` where the timestamp is the\n`X-SBS-Timestamp` header, and compare with `X-SBS-Signature` in constant\ntime. Reject anything whose timestamp is more than five minutes old.\n\nLost the secret? Send `rotateSecret: true` — there is no endpoint that\ncan read it back.\n\n`PUT /webhooks` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"url\": \"https://example.co.tz/hooks/sbs\",\n  \"events\": [\n    \"message.sent\"\n  ],\n  \"enabled\": true,\n  \"secret\": \"\",\n  \"createdAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Keys",
      "description": "Managing the key you are calling with.",
      "item": [
        {
          "name": "Rotate the key you are calling with",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/keys/rotate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "keys",
                "rotate"
              ]
            },
            "description": "Issues a new key and revokes the current one **immediately**. There is\nno overlap window, so deploy the new value everywhere it is used before\nyou rely on it.\n\nThe new key is returned once. It is not stored anywhere you can read it\nback.\n\n`POST /keys/rotate` — full reference: https://docs.selanimsms.co.tz"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/keys/rotate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "keys",
                    "rotate"
                  ]
                },
                "description": "Issues a new key and revokes the current one **immediately**. There is\nno overlap window, so deploy the new value everywhere it is used before\nyou rely on it.\n\nThe new key is returned once. It is not stored anywhere you can read it\nback.\n\n`POST /keys/rotate` — full reference: https://docs.selanimsms.co.tz"
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "cookie": [],
              "body": "{\n  \"apiKey\": \"sbs_live_9d1e4c8b2a7f6e3d0c9b8a7f6e5d\",\n  \"rotatedAt\": \"2026-01-01T09:00:00Z\"\n}"
            }
          ]
        }
      ]
    }
  ]
}
