The Textly API will be available at https://api.textly.co.ke. All examples below show paths relative to that origin.
Authentication
Protected requests use Authorization: Bearer <token>. Account setup routes use a customer session. Sending and read endpoints accept either a customer session or an active API key for that organization.
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /auth/register | Create an organization and owner session |
| POST | /auth/login | Create a seven-day session |
| GET | /auth/me | Inspect the current identity |
| POST | /auth/logout | Revoke a session |
| GET / POST | /api-keys | List or create API keys |
| POST | /api-keys/:id/revoke | Revoke an API key |
Send messages
Provide an approved senderId, a messages array, and an Idempotency-Key header. Optional campaignName labels the batch. Optional scheduledAt is an ISO 8601 timestamp with timezone, up to one year ahead.
{
"senderId": "approved-sender-uuid",
"campaignName": "Customer update",
"messages": [
{ "recipient": "254712345678", "text": "Hello Amina" },
{ "recipient": "0712345679", "text": "Hello Musa" }
]
}Textly normalizes Kenyan mobile numbers to 254XXXXXXXXX, rejects duplicate recipients in one request, checks suppressions, and reserves the full charge in one database transaction. Requests support up to 1000 recipients; each text is limited to ten calculated SMS segments.
To send to a saved group, provide groupId and either text or templateId instead of the messages array. Group sends support up to 10,000 contacts. Opted-out and suppressed contacts are excluded. A name placeholder in the text or template personalizes each recipient's message.
Pass the same send body without an idempotency key to review recipients, rendered message samples, segments, estimated charge, and wallet sufficiency before sending. The final send recalculates the charge and reserves funds.
The same idempotency key with the same payload returns the original response. Reusing the key for changed content returns 409 Conflict.
Returns your messages, newest first. Use limit (1–500), offset, status, and recipient query parameters to page and filter delivery history.
Returns one message, including its current status, segment count, charge, and timestamps.
Message states
| Status | Meaning |
|---|---|
queued | Accepted by Textly and waiting to dispatch |
submitting | Worker has claimed the message |
accepted | Onfon returned a provider message ID |
delivered | Provider receipt reports delivery |
failed | Provider receipt reports delivery failure |
rejected | Provider explicitly rejected the submission |
unknown | Submission outcome needs reconciliation |
cancelled | Queued message was cancelled before dispatch |
Campaigns
Every send request has a request ID and appears in campaign views. GET /campaigns lists recent batches, GET /campaigns/:id shows the summary and first 100 messages, and GET /campaigns/:id/messages?limit=100&offset=0 pages through the full audience. POST /campaigns/:id/cancel cancels messages still queued. Already submitted messages remain in their current states.
Workspace endpoints
| Resource | Endpoints |
|---|---|
| Wallet and rate | GET /wallet, GET /wallet/entries, GET /pricing |
| Reports | GET /reports/overview?days=30 for status totals, charges, and daily counts |
| Sender IDs | GET /senders, POST /senders with optional business and network details |
| Contacts | GET /contacts, POST /contacts, POST /contacts/import, PUT /contacts/:id, DELETE /contacts/:id |
| Groups | GET /contact-groups, POST /contact-groups, GET /contact-groups/:id/contacts, PUT /contact-groups/:id, DELETE /contact-groups/:id |
| Templates | GET /templates, POST /templates, PUT /templates/:id, DELETE /templates/:id |
| Suppressions | GET /suppressions, POST /suppressions |
| Team | GET /team, POST /team/invitations, POST /auth/accept-invite |
Amounts use integer KES cents, represented as decimal strings. Segment counts and charges are calculated by Textly; confirm your pricing and billing agreement before sending at scale.
Errors and retries
Expect standard HTTP status codes: 400 for invalid input or insufficient wallet balance, 401 for missing or invalid credentials, 403 for a role restriction, and 409 for an idempotency conflict. Retry a send with the same key and payload after a client-side timeout.