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.

CredentialWho holds itUsed for
API key (pk_live_… / pk_test_…)Your backend onlyProvisioning users, minting user tokens, webhooks, call history, audit logs.
End-user JWTOne signed-in end-userEverything a user does: conversations, messages, attachments, calls, devices. Minted by your backend with POST /v1/users/:user_id/tokens.
Call grantsA phone's native call codeAnswering, declining and controlling a call without a user session. See Calls.
Reply grantA phone's notification codeReplying to or marking read a message notification. See Notification actions.

See Authentication for scopes, token lifetimes and revocation.

Endpoint groups

API key

PageEndpoints
whoamiGET /v1/whoami
UsersPOST /v1/users, POST /v1/users/:user_id/tokens, POST /admin/v1/tokens/revoke
Call historyGET /v1/calls, GET /v1/calls/:id
Webhooks/v1/webhooks…
Audit logsGET /v1/audit_logs

End-user JWT

PageEndpoints
Current userGET /v1/me, PATCH /v1/me
ConversationsConversations and members
MessagesSend, list, edit, delete, threads, reactions, mark read
AttachmentsPresigned upload and download URLs
CallsPlace, join, answer, decline, hang up, ring more people, media tokens
Call historyGET /v1/me/calls, GET /v1/conversations/:conversation_id/calls
DevicesRegister push tokens

Grants

PageEndpoints
CallsPOST /v1/calls/:id/answer, decline, hangup, reconnect, ring
Notification actionsPOST /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/users returns the user object itself. Exceptions are called out on each page: GET /v1/calls/:id returns { "data": { … } }, and POST /v1/webhooks returns { "webhook": { … }, "signing_secret": "…" }.
  • Lists are wrapped: { "data": [ … ] }, plus next_cursor on endpoints that paginate with a cursor.
  • 204 No Content responses 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:

StyleEndpointsHow
Opaque cursorGET /v1/calls, GET /v1/me/calls, GET /v1/conversations/:conversation_id/calls, GET /v1/audit_logsThe response has a top-level next_cursor. Pass it back as ?cursor=. null means there are no more rows.
Sequence cursorGET /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, ring and /v1/notifications/*) ignore the header.

Rate limits

poolse has a small number of fixed abuse limits. They are the same on every plan.

LimitApplies toWhen hit
60 requests per minute per API keyPOST /v1/users/:user_id/tokens429 rate_limited with a Retry-After header (seconds).
50 new users per hour per end-userUsers created implicitly by member_external_ids / external_ids429 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.

GET/healthz
No auth

Liveness. Returns 200 with the body ok while the server responds.

GET/readyz
No auth

Readiness. 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.

GET/openapi.json
No auth
GET/swaggerui
No auth

The spec is generated from the server code. Where it and these pages disagree, these pages describe what the server actually returns.

API reference · poolse docs