Browse documentation

Migration and reference

REST API and OpenAPI

Inspect the versioned /v1 HTTP contract when integrating without the Node.js SDK.

DevelopersAdvancedAvailable

What this guide covers

  • Send authenticated JSON requests to the stable /v1 API.
  • Use the OpenAPI contract for endpoint, schema, and response details.
  • Rely on public Yanola IDs rather than internal database identifiers.

Use the published contract

Yanola publishes the complete Developer Platform REST contract as OpenAPI 3.1 JSON. It is the machine-readable source for paths, parameters, request and response schemas, authentication, examples, headers, and public error responses.

The checked-in artifact is served with the documentation site and is copied byte-for-byte from the backend source contract. Download it for local inspection, validation, mock generation, or client tooling.

Stable REST /v1 and webhook compatibility

REST /v1 will not remove or rename documented endpoints or fields, make optional request fields required, change documented field types incompatibly, reuse stable error codes with different meanings, or change accepted behavior incompatibly. A breaking protocol change requires a new major API base path.

Compatible evolution can add endpoints, optional capabilities, optional response fields, distinguishable error codes, explicitly subscribed webhook event types, and fields inside extensible normalized event data. Ignore unknown response and webhook fields. A deprecated /v1 operation remains part of /v1 even after its replacement is documented.

GA contract stability
AreaStable contractCompatible evolution
REST /v1Paths, required inputs, public IDs, field meanings, error envelope, and successful behavior.New endpoints and optional response fields.
yanola webhook modeNormalized Event envelope, signed headers, payload-mode identity, and public IDs.New optional event data and explicitly subscribed event types.
yanola_with_raw modeNormalized envelope plus a required nullable raw field.Retained provider content can vary; raw becomes null when unavailable or expired.
meta webhook modeUnwrapped object / entry / changes / value structure.Provider-compatible value fields can be added; the body is never promised as a byte-for-byte Meta replay.
Breaking changeNever applied in place to /v1.Published under a new major API base path and corresponding SDK major when required.

Make an authenticated REST request

Send every request to the production /v1 base URL over HTTPS. Put one Yanola API Key in the Authorization header using the Bearer scheme and keep that key in server-side environment configuration.

The key resolves the Yanola Account, Agent Project, environment, scopes, and optional Sender restrictions. A Test Mode key cannot read or mutate Live Mode data, and a Live Mode key cannot access Test Mode data.

POST /v1/messages/text
bash
curl --request POST \
  --url https://api.yanola.ai/v1/messages/text \
  --header "Authorization: Bearer $YANOLA_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: send-order-123" \
  --data '{
    "from": "+15557654321",
    "to": "+15551234567",
    "body": "Hello from Yanola",
    "previewUrl": false
  }'

Published request and response examples

These JSON blocks are exact copies of named examples in the published OpenAPI document. The SDK contract test parses the same component examples and validates them against their assigned OpenAPI schemas before exercising the backend routes.

TextSendLegacyPhoneRequest OpenAPI example
json
{
  "from": "+15557654321",
  "to": "+15551234567",
  "body": "Legacy phone clients can migrate the base path first.",
  "previewUrl": false
}
MessageAcceptedLive OpenAPI example
json
{
  "id": "msg_0123456789ABCDEabcdeWXYZ",
  "environment": "live",
  "status": "queued",
  "from": "+15557654321",
  "to": "+15551234567",
  "requestId": "req_0123456789",
  "createdAt": "2026-07-21T12:00:00.000Z"
}

Message operations

REST message operations
OperationPurposeSuccess
POST /messages/textQueue a text message.202
POST /messages/templateQueue an approved template message.202
POST /messages/mediaQueue image, video, document, or audio from a public URL.202
POST /messages/locationQueue latitude and longitude with optional name and address.202
POST /messages/contact-cardQueue one or more WhatsApp contact cards.202
POST /messages/{messageId}/mark-readMark a Yanola-issued inbound message ID as read.200
GET /messagesList scoped message logs with filters and cursor pagination.200
GET /messages/{messageId}Retrieve one scoped public message log entry.200

Catalog and media operations

REST catalog and media operations
OperationPurposeSuccess
GET /sendersList connected Senders visible to the API key.200
GET /templatesList approved templates for an E.164 Sender.200
GET /media/{mediaId}Retrieve inbound media metadata and a short-lived signed proxy URL.200

Event, delivery, and usage operations

REST event, delivery, and usage operations
OperationPurposeSuccess
GET /eventsList retained, normalized Developer Platform events.200
GET /events/{eventId}Retrieve one normalized event.200
GET /webhook-deliveriesList scoped webhook delivery summaries.200
GET /webhook-deliveries/{deliveryId}Retrieve one webhook delivery summary.200
POST /webhook-deliveries/{deliveryId}/replayCreate a new pending delivery from a failed source delivery.202
GET /usageList immutable usage and Yanola Balance pricing snapshots.200

Use idempotency for safe mutations

Send an Idempotency-Key when your application may retry a send, mark-read, or delivery-replay request. The value must be 8 through 255 characters.

Yanola scopes the key by Agent Project, environment, concrete endpoint, API key, and request-body hash. Repeating the same operation with the same body returns the original outcome; reusing the key with a different body returns 409 idempotency_key_reused.

  1. Generate one stable key for one logical mutation, such as an order notification.
  2. Persist the key with your job or outbox record before the first network attempt.
  3. Reuse it only when retrying the identical endpoint and body.
  4. Create a new key when the intended mutation or body changes.

Paginate and filter list operations

List endpoints use opaque cursor pagination. Omit limit for the default of 20, or request 1 through 100 items. When pagination.hasMore is true, pass pagination.nextCursor unchanged as the next cursor.

Filters are endpoint-specific. Common filters include environment, from, status, created_after, and created_before. Date filters use RFC 3339 date-time strings. Do not parse, construct, or reuse a cursor with a different filter set.

Follow an opaque cursor
bash
curl --get "https://api.yanola.ai/v1/messages" \
  --header "Authorization: Bearer $YANOLA_API_KEY" \
  --data-urlencode "limit=50" \
  --data-urlencode "environment=live" \
  --data-urlencode "cursor=$YANOLA_NEXT_CURSOR"

Distinguish protocol and account limits

Fixed request-shape limits are part of the GA protocol. Commercial capacity is account-specific and must be discovered from the current subscription entitlement and live response headers; development seed tiers are not a public price or limit promise.

Public GA limit sources
LimitGA value or sourceIntegration behavior
List pagination20 by default; request 1 through 100.Follow the opaque next cursor without changing filters.
Idempotency-Key8 through 255 characters.Persist one key per logical mutation and identical body.
Contact cards1 through 10 contacts per send.Split a larger application operation deliberately.
Webhook signature timeSDK default tolerance is five minutes.Verify the timestamp and exact raw body before parsing.
REST throughputx-ratelimit-limit, remaining, reset, and retry-after.Size concurrency from each response rather than a plan-name guess.
Keys, endpoints, projects, Test Mode, retention, and supportCurrent account entitlement in Billing.Do not hard-code seed plan names or quantities.

Read response and rate-limit headers

Public REST response headers
HeaderMeaningAction
x-request-idYanola request ID for support and debugging.Record it with the operation result, never with API key material.
x-ratelimit-limitMaximum requests in the current window.Use it to size concurrency conservatively.
x-ratelimit-remainingRequests remaining in the current window.Slow down before capacity reaches zero.
x-ratelimit-resetUnix timestamp when the window resets.Schedule later work against the reset time.
retry-afterSeconds to wait after 429 or a temporary failure.Wait at least this long and add jitter before retrying.

Handle the public error envelope

Every documented JSON error response uses one stable envelope. Branch on error.code for program behavior, use retryable and HTTP status to decide retry policy, and retain requestId for support correlation.

ErrorResponse schema shape
json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after the time in the retry-after header.",
    "requestId": "req_0123456789",
    "retryable": true,
    "details": {}
  }
}
Documented public error statuses
StatusCategoryTypical action
400Malformed or invalid requestCorrect the request before retrying.
401Invalid API keyReplace or rotate the credential.
402Plan Message Cap reachedwait for the next period or upgrade the Plan; replies inside the 24-hour window, free-form or template, still go out.
403Missing permission or entitlementChange the API-key scope or subscription access.
404Missing or inaccessible public resourceVerify the ID and key scope without probing other projects.
409Idempotency conflictUse the original body or a new key for a new operation.
422Business state prevents the operationFollow the action in the error details; do not blindly retry.
429Rate limit exceededHonor retry-after and add jitter.
500Unexpected API failureRetry only when safe and keep the request ID.
503Temporary dependency failureHonor retry-after and preserve idempotency.

Understand example coverage

Every named component example has one declared contract schema. The SDK contract test fails when an example is added without a mapping, when a mapped example violates its schema, or when a real backend response violates the operation response schema.

OpenAPI component example schema coverage
ExamplesValidated schemaCoverage
TextSendLegacyPhoneRequest, TextSendTypedPhoneRequest, TextSendRegularBsuidRequest, TextSendParentBsuidRequestTextMessageSendRequestPhone and typed-recipient text requests.
TemplateSendLegacyPhoneRequest, TemplateSendRegularBsuidRequestTemplateMessageSendRequestApproved template name, language, components, and parameters.
MediaSendLegacyPhoneRequest, MediaSendRegularBsuidRequestMediaMessageSendRequestPublic media URL and optional caption.
LocationSendLegacyPhoneRequest, LocationSendRegularBsuidRequestLocationMessageSendRequestPhone and typed-recipient locations.
ContactCardSendLegacyPhoneRequest, ContactCardSendRegularBsuidRequestContactCardMessageSendRequestPhone and typed-recipient contact cards.
MessageAcceptedLive, TypedMessageAcceptedRegularBsuidMessageAcceptedResponseQueued phone and typed-recipient public message IDs.
MalformedRequestError, InvalidApiKeyErrorErrorResponseParsing and authentication failures.
InsufficientBalanceError, IdempotencyKeyReusedErrorErrorResponseBilling and safe-retry conflicts.
ServiceWindowRequiredError, SenderReconnectRequiredErrorErrorResponseNon-retryable Sender and conversation state.
RateLimitedErrorErrorResponseRetryable capacity failure.
SandboxQuotaExhaustedError, SandboxMessageTypeUnsupportedErrorErrorResponseSandbox quota and message policy failures.
WhatsappPhoneOnlyTemplateError, WhatsappRecipientScopeMismatchError, WhatsappRouteCapabilityUnavailableError, WhatsappTemplateCapabilityUnknownErrorErrorResponseTyped-recipient capability and scope failures.

Generate clients carefully

Use tooling that supports OpenAPI 3.1 and JSON Schema 2020-12 semantics. Resolve local component references, preserve string formats and regex constraints, and validate generated request types before trusting generated code.

Generated clients do not replace operational behavior. Keep API keys server-side, preserve idempotency keys across retries, honor retry-after, accept additive response fields, and follow asynchronous message state through logs or events.

Verify the contract locally

The canonical JSON lives in the backend project. The frontend sync command republishes it byte-for-byte, the frontend check rejects drift, the backend suite verifies REST invariants and local references, and the SDK suite validates every example plus real route responses.

Republish after an intentional backend contract change
bash
cd yanola.ai
npm run openapi:sync
Run the cross-project OpenAPI gates
bash
cd yanola.ai
npm run openapi:check
npm run test:developer-platform -- --runTestsByPath src/app/docs/docs-structure.spec.tsx

cd ../yanola.ai.be
npx jest src/developer-platform/openapi/openapi-v1.spec.ts --config ./jest.config.cjs --runInBand

cd ../yanola.whatsapp
npm run test:contract