Requests and retries
Every Ponitor API uses JSON over HTTPS with your API key in a header. Each request carries your own externalId, so you can retry safely.
Format
- Use HTTPS and the base URL of the API you're calling.
- Send JSON with
Content-Type: application/json. Responses are JSON. - Send your key in the
apiKeyheader. See API keys and base URLs. - Times are ISO 8601 in UTC, such as
2026-10-05T09:30:00.000Z. - Money is in US dollars, as a number.
0.05is five cents.
Phone numbers
Write numbers without spaces or dashes. The two APIs take different forms:
- Messaging API: international format, with or without the
+:+231770000000or231770000000. Not a leading0. - Airtime API: digits only:
231770000000, or the local form0770000000. A+is refused.
Messages show numbers with the +, in responses and in webhooks. Airtime purchases and USSD requests show them without it.
Retry safely with externalId
Every request that sends a message or makes a purchase carries an externalId: your own reference, such as an order number. It must be unique within your team. The Messaging API takes up to 64 characters. The Airtime API takes up to 30 letters, digits, _, . or -, so a UUID (36 characters) is too long there.
If a request times out, you can't tell whether Ponitor received it. Send it again with the same externalId. If the first request went through, you get that message or purchase back with 200 OK. Nothing is sent or charged a second time.
- Use a new
externalIdfor each new request. Reuse one only to retry the same request. If you send different content with a usedexternalId, you get the original back. externalIds are scoped to your team. They never clash with another team's.- Retry after a timeout, a
429or a5xx. Wait longer before each attempt, for example 1, 2, 4 and 8 seconds. - Don't retry a
400unchanged. Fix the request first.
Responses
A Messaging API response wraps the object in a key.
{ "message": { "id": "e2c38865-dc0a-4299-baef-c07a9bfa9c33", "status": "QUEUED" } }
An Airtime API response is the object itself. Each endpoint's page shows its exact response.
An error has an error object with a code that never changes. A request that fails validation lists each field at fault in details.
{
"error": {
"type": "validation_error",
"code": "E01001",
"name": "ValidationError",
"message": "The request has 1 problem.",
"doc_url": "https://docs.ponitor.com/api/errors#E01001",
"request_id": "c1f0b7a2-5e1d-4c8e-9a0b-7d6e5f4c3b2a",
"param": "externalId",
"details": [{ "param": "externalId", "message": "externalId is required" }]
},
"message": "The request has 1 problem."
}
Branch on error.code, not the message text. The text can change. Errors lists every code.
Timeouts and limits
- Wait 30 seconds before you treat a request as timed out. Most requests answer in under a second.
- Too many requests in a minute return
429 Too Many Requests. Wait and retry as described above. See Rate limits. - Use a webhook to follow a message or a purchase instead of polling.