Base URL and format
Content-Type: application/json and authenticate with Authorization: Bearer … (Authentication). Call the API from your server only: keys must never reach a browser or mobile app.
- Timestamps are ISO 8601 in UTC, for example
2026-10-14T17:00:00.000Z. - Phone numbers are E.164, for example
+15555550123. Recipients must be US numbers. - Services are
iMessage,RCSandSMSin API responses. Webhook payloads use lowercaseimessage,rcsandsms. - Unknown body fields are rejected with
VALIDATION_ERROR, and so is a body that isn’t valid JSON.
Responses
Every successful response has the same envelope. The result is indata:
status: "error" and an error object with a stable code. Check status before you read data: an error response has no data. See Errors.
IDs
Resource IDs are opaque strings with a prefix that tells you the type. Store them as strings and don’t parse them. (Credential IDs inGET /v1/me are plain UUIDs.)
An ID with the wrong prefix for an endpoint returns
INVALID_PUBLIC_ID. IDs from another workspace return NOT_FOUND.
Pagination
Most list endpoints return a page of results and a cursor. These return everything at once, with no cursor:GET /v1/phone-numbers, GET /v1/contacts/search (up to limit matches), GET /v1/workflows, GET /v1/team-members/assignable and GET /v1/webhook-subscriptions.
limitsets the page size: 1 to 100, default 25. Contact search is the exception: 1 to 25, default 10, with no cursor.- While
has_moreistrue, passnext_cursorascursorto get the next page. Keep the same filters. - Cursors are opaque and tamper-proof. A changed or foreign cursor returns
INVALID_CURSOR. Leavecursorout of the first request; a placeholder such ascursor=stringis rejected the same way.
Tracing requests
- Every response includes
trace_idin the body and anX-Trace-IDheader. - Send your own
X-Request-IDheader to correlate with your logs. Blujay echoes it back asclient_request_idin error responses and in most success responses. The send responses (POST /v1/chats,POST /v1/chats/{chat_id}/messagesandPOST /v1/messages/{message_id}/retry) leave it out, so map your request ID to the returnedmessage.idyourself there.
trace_id, your X-Request-ID and the time of the request. Never include your API key.
Retries and timeouts
Use a client timeout of about 30 seconds. If a write times out or returns a retryable error, retry it with the sameIdempotency-Key, so a request that actually succeeded is never repeated. See Idempotency and Rate limits.
Time zones and reset_at
Every timestamp is UTC. One limit follows a local clock: a number’s new-contact count resets at midnight in your workspace’s time zone, which the API doesn’t expose. Don’t compute the reset yourself. Read limits.new_contacts.reset_at on the number, or error.details[0].reset_at on a 429, and wait until then. See Phone numbers and limits.
Versioning
The version is in the path:/v1. Within v1, Blujay works to keep a well-written client working.
Changes Blujay can make at any time:
- adding endpoints, optional request fields and response fields;
- adding error codes for new situations, webhook event types and enum values;
- rewording
error.messageand other text meant for people; - adjusting sending limits and messaging-safety rules to protect delivery. These always use documented error codes, and Dedicated numbers report their current limits in
GET /v1/phone-numbers.
- removing or renaming an endpoint, field, error code or enum value;
- making an optional request field required;
- changing what a field means or its type;
- returning a different error code for the same situation;
- changing a webhook payload within a version you’ve pinned.
error.code, never on error.message. Webhook payload shapes are versioned separately by date, and a subscription stays on the version you pin. See Webhook versions.
Next steps
Idempotency
Retry safely without sending twice.
Rate limits
Request limits and how to back off.
Phone numbers and limits
Sending limits and the live fields that report them.
Errors
Every error code and whether it is retryable.