REST API reference
The poolse REST API speaks HTTPS and JSON. Base URL:
https://api.poolse.dev
Send request bodies as JSON with Content-Type: application/json.
Credentials
Every endpoint takes exactly one kind of credential, sent as
Authorization: Bearer <credential>. Each endpoint's page says which.
| Credential | Who holds it | Used for |
|---|---|---|
API key (pk_live_… / pk_test_…) | Your backend only | Provisioning users, minting user tokens, webhooks, call history, audit logs. |
| End-user JWT | One signed-in end-user | Everything a user does: conversations, messages, attachments, calls, devices. Minted by your backend with POST /v1/users/:user_id/tokens. |
| Call grants | A phone's native call code | Answering, declining and controlling a call without a user session. See Calls. |
| Reply grant | A phone's notification code | Replying to or marking read a message notification. See Notification actions. |
See Authentication for scopes, token lifetimes and revocation.
Endpoint groups
API key
| Page | Endpoints |
|---|---|
| whoami | GET /v1/whoami |
| Users | POST /v1/users, POST /v1/users/:user_id/tokens, POST /admin/v1/tokens/revoke |
| Call history | GET /v1/calls, GET /v1/calls/:id |
| Webhooks | /v1/webhooks… |
| Audit logs | GET /v1/audit_logs |
End-user JWT
| Page | Endpoints |
|---|---|
| Current user | GET /v1/me, PATCH /v1/me |
| Conversations | Conversations and members |
| Messages | Send, list, edit, delete, threads, reactions, mark read |
| Attachments | Presigned upload and download URLs |
| Calls | Place, join, answer, decline, hang up, ring more people, media tokens |
| Call history | GET /v1/me/calls, GET /v1/conversations/:conversation_id/calls |
| Devices | Register push tokens |
Grants
| Page | Endpoints |
|---|---|
| Calls | POST /v1/calls/:id/answer, decline, hangup, reconnect, ring |
| Notification actions | POST /v1/notifications/reply, POST /v1/notifications/read |
Response envelopes
Successful responses are JSON. Their shape depends on the endpoint:
- Single resources are returned bare, for example
POST /v1/usersreturns the user object itself. Exceptions are called out on each page:GET /v1/calls/:idreturns{ "data": { … } }, andPOST /v1/webhooksreturns{ "webhook": { … }, "signing_secret": "…" }. - Lists are wrapped:
{ "data": [ … ] }, plusnext_cursoron endpoints that paginate with a cursor. 204 No Contentresponses have an empty body.
Every error, including unknown routes, malformed JSON and server crashes, uses one envelope:
{
"error": {
"code": "not_found",
"message": "Not found.",
"doc_url": "https://poolse.dev/docs/api/errors#not_found"
}
}
Switch on error.code. See Errors for every code.
Any path segment that names an id (:id, :conversation_id,
:user_id, :delivery_id, …) must be a UUID. A value that is not a
UUID gets 404 not_found. An id in the body or query string that is not
a UUID gets 400 bad_request.
Pagination
Two styles, depending on the endpoint:
| Style | Endpoints | How |
|---|---|---|
| Opaque cursor | GET /v1/calls, GET /v1/me/calls, GET /v1/conversations/:conversation_id/calls, GET /v1/audit_logs | The response has a top-level next_cursor. Pass it back as ?cursor=. null means there are no more rows. |
| Sequence cursor | GET /v1/conversations/:conversation_id/messages (?before=<sequence>), GET /v1/messages/:id/replies (?after=<sequence>) | Pass the sequence of the last message you have. The response has no cursor field: stop when you get fewer rows than limit. |
GET /v1/conversations, GET /v1/webhooks, GET /v1/webhooks/:id/deliveries
and the member list do not paginate. See each page for its limits.
Idempotency
Send an Idempotency-Key header on a POST, PUT, PATCH or DELETE
to make a retry safe:
curl -X POST https://api.poolse.dev/v1/users \
-H "Authorization: Bearer $POOLSE_API_KEY" \
-H "Idempotency-Key: 8c7e2f9a-3d1b-4e6f-9a2c-5b7d8e0f1a23" \
-H "Content-Type: application/json" \
-d '{"external_id":"u_42"}'
How it works:
- The first 2xx response is stored for 24 hours. A repeat request
with the same key returns that stored status and body, with the header
Idempotency-Replayed: true, and does not run again. - Error responses are not stored. You can retry a failed request with the same key.
- A key is scoped to your tenant, the caller (the API key or the end-user), the method and the path. The same key on a different path, or from a different user, is a different key.
- The request body is not compared. Reusing a key with a different body returns the stored response from the first request.
- It works on API-key and end-user-JWT endpoints. The grant-authorized
endpoints (
/v1/calls/:id/answer,decline,hangup,reconnect,ringand/v1/notifications/*) ignore the header.
Rate limits
poolse has a small number of fixed abuse limits. They are the same on every plan.
| Limit | Applies to | When hit |
|---|---|---|
| 60 requests per minute per API key | POST /v1/users/:user_id/tokens | 429 rate_limited with a Retry-After header (seconds). |
| 50 new users per hour per end-user | Users created implicitly by member_external_ids / external_ids | 429 lazy_provision_rate_limited with a Retry-After header (seconds) and details.retry_after_ms. |
When you get a 429, wait for Retry-After seconds before retrying.
Plan quotas (end-user count, webhook count, attachment storage) are
different: they return 402, and only upgrading lifts them. See
Limits & quotas.
Health checks
Neither endpoint needs a credential. Both return text/plain.
/healthzLiveness. Returns 200 with the body ok while the server responds.
/readyzReadiness. Returns 200 with ok when the database answers, and 503
with repo unavailable when it does not.
OpenAPI
The server publishes an OpenAPI 3 spec and a Swagger UI. Neither needs a credential.
/openapi.json/swaggeruiThe spec is generated from the server code. Where it and these pages disagree, these pages describe what the server actually returns.