Documentation

Developers

REST API

Read conversations, messages and orders and act on restaurant orders with Sela's server-to-server REST API, including auth, errors and rate limits.

Last updated: 2026-09-29

The Sela REST API is a small, read-mostly, server-to-server API. It lets your backend list conversations and messages, list restaurant orders, and approve, reject or complete an order. It is versioned under /api/v1.

Access and plans#

  • Plan: API keys are available on the Scale and Enterprise plans, and API access must be enabled for your workspace (contact support if it is not). The AI Assist add-on is not required. See Plans and billing.
  • If the plan lapses: every existing key is treated as invalid and calls return 401 unauthorized.
  • Who can create keys: workspace admins, in the dashboard under API Keys.

Base URL#

text
https://jovial-bison-730.convex.site

All examples below use it through an environment variable:

bash
export SELA_API_BASE="https://jovial-bison-730.convex.site"
export SELA_API_KEY="sk_live_..."

Create an API key#

  1. Open API Keys in the dashboard and create a key.
  2. Give it a name and choose the permissions it needs.
  3. Optionally set a rate limit (requests per minute) and an expiry date.
  4. Copy the key immediately. It is shown once. Sela stores only a SHA-256 hash and cannot show it again.

Keys always look like sk_live_ followed by 64 hex characters, even when the environment label is test; the label is only a tag. Revoking a key disables it at once; deleting it removes it.

Permissions#

PermissionAllows
conversations:readGET /api/v1/conversations
messages:readGET /api/v1/messages
orders:readGET /api/v1/orders
orders:writePOST /api/v1/orders/action

Grant the smallest set each integration needs. A missing permission returns 403 forbidden.

Authentication#

Send the key as a bearer token on every request:

bash
curl "$SELA_API_BASE/api/v1/conversations" \
  -H "Authorization: Bearer $SELA_API_KEY"

Requests and responses#

  • Responses are application/json with Cache-Control: no-store.
  • Every response carries X-Sela-Request-Id. If you send an X-Request-Id header, Sela echoes it; otherwise it generates a UUID. Include this ID when you contact support.
  • Success bodies use { "data": ..., "meta": { "request_id": "...", ... } }.
  • Timestamps are Unix milliseconds.

Pagination#

List endpoints accept:

ParameterDefaultRangeMeaning
limit501 to 100Page size.
cursorNoneOpaqueThe meta.next_cursor value from the previous page.

meta includes next_cursor (string or null), is_done (boolean) and limit. Keep requesting with the returned cursor until is_done is true. Do not build cursors yourself.

Errors#

Errors use one envelope:

json
{
  "error": {
    "code": "forbidden",
    "message": "Missing orders:write permission.",
    "request_id": "0b2f..."
  }
}
HTTPcodeWhen
400invalid_requestBad or missing parameters, malformed id, invalid status or action, or a body that is not a JSON object.
401unauthorizedMissing bearer token, or the key is invalid, revoked, expired, or the plan is no longer eligible.
403forbiddenThe key lacks the required permission.
404not_foundThe order does not exist or is not an order of your workspace.
409invalid_stateThe order cannot make that transition.
429rate_limitedThe key exceeded its per-minute limit.

Rate limits#

Rate limits are set per key when you create it, as a positive number of requests per minute. If you leave it empty the key is not throttled, so set one for every key you hand to a third party. The window is a fixed 60-second window.

A 429 response includes extra fields inside error:

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key rate limit exceeded.",
    "request_id": "0b2f...",
    "retry_after_seconds": 12,
    "limit": 60
  }
}

There is no Retry-After header. Read error.retry_after_seconds and back off.

GET /api/v1/conversations#

Requires conversations:read. Returns conversations, most recently updated first.

Query parameterValuesNotes
statusopen, closed, escalatedAny other value is ignored (no filter).
limit, cursorSee pagination
bash
curl "$SELA_API_BASE/api/v1/conversations?status=open&limit=25" \
  -H "Authorization: Bearer $SELA_API_KEY"
json
{
  "data": [
    {
      "id": "j57abc...",
      "status": "open",
      "channel": "whatsapp",
      "subject": "Order help",
      "isSolved": false,
      "createdAt": 1712345600000,
      "updatedAt": 1712345678901,
      "lastMessageAt": 1712345678901
    }
  ],
  "meta": {
    "request_id": "...",
    "next_cursor": "...",
    "is_done": false,
    "limit": 25,
    "status": "open"
  }
}

subject is optional. channel is one of web, whatsapp, messenger, instagram, telegram, email, sms, voice. meta.status is the applied filter, or null.

GET /api/v1/messages#

Requires messages:read. Returns the messages of one conversation, newest first.

Query parameterRequiredNotes
conversation_idYesThe id from the conversations list. Missing or malformed returns 400 invalid_request.
limit, cursorNoSee pagination.
bash
curl "$SELA_API_BASE/api/v1/messages?conversation_id=j57abc...&limit=50" \
  -H "Authorization: Bearer $SELA_API_KEY"
json
{
  "data": [
    { "id": "k17def...", "direction": "contact", "text": "Hello", "createdAt": 1712345678901 }
  ],
  "meta": {
    "request_id": "...",
    "next_cursor": null,
    "is_done": true,
    "limit": 50,
    "conversation_id": "j57abc..."
  }
}

direction is contact (the customer), agent (the assistant or a team member) or system. Only text and timestamps are exposed: no attachments, sender ids, or an AI-versus-human flag. A conversation id that belongs to another workspace returns an empty page with is_done: true, not an error.

GET /api/v1/orders#

Requires orders:read. Returns restaurant orders created by the assistant's food-order tool. If your workspace has none, the result is an empty page with is_done: true. See Restaurant order integration for the full flow.

Query parameterValuesNotes
statuspending, approved, arrived, rejected, failed, cancelled, allDefault all. Any other value returns 400 with the allowed list.
limit, cursorSee pagination
bash
curl "$SELA_API_BASE/api/v1/orders?status=pending" \
  -H "Authorization: Bearer $SELA_API_KEY"
json
{
  "data": [
    {
      "id": "kx7...",
      "status": "pending",
      "createdAt": 1712345678901,
      "updatedAt": 1712345678901,
      "conversationId": "j57abc...",
      "customer": { "id": "...", "name": "Ali", "phone": "+9647700000000", "email": null },
      "fulfillment": { "type": "delivery", "address": "Al Mansour" },
      "orderSummary": "2 burgers, fries",
      "specialInstructions": null,
      "details": {},
      "externalOrderId": null
    }
  ],
  "meta": { "request_id": "...", "next_cursor": null, "is_done": true, "limit": 50, "status": "pending" }
}

Field notes:

  • status values: pending (waiting for approval), approved (approved or in progress), arrived (completed), rejected, failed, cancelled.
  • conversationId, customer.*, fulfillment.*, specialInstructions and externalOrderId can be null.
  • fulfillment.type is delivery, pickup or null.
  • details is the raw order input captured by the assistant.

POST /api/v1/orders/action#

Requires orders:write. Approves, rejects or completes one order.

Body fieldRequiredNotes
order_idYesThe order id.
actionYesapprove, reject or mark_arrived.
external_order_idNoYour own order reference. Trimmed and cut to 256 characters, and stored on the order.
bash
curl -X POST "$SELA_API_BASE/api/v1/orders/action" \
  -H "Authorization: Bearer $SELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"kx7...","action":"approve","external_order_id":"menu-123"}'
json
{
  "data": { "id": "kx7...", "status": "approved", "externalOrderId": "menu-123" },
  "meta": { "request_id": "...", "changed": true, "event": "order.approved" }
}

data is the full order object shown above (shortened here). meta.changed says whether the state moved, and meta.event is the webhook event that was sent, or null.

Allowed transitions#

Current statusapproverejectmark_arrived
pendingChanges, sends order.approvedChanges, sends order.rejected409
approvedNo change (changed: false)409Changes, sends order.arrived
rejected409No change409
arrived409409No change
failed, cancelled409409409

Repeating an action that already took effect returns 200 with changed: false and no event, so retries are safe. A supplied external_order_id is still applied. Illegal moves return 409 invalid_state, for example Cannot mark_arrived an order with status pending.

Side effects:

  • approve and reject send an assistant message to the customer's conversation. If that message cannot be delivered the failure is logged and the call still succeeds. You cannot supply a custom customer message through the API.
  • The matching outbound webhook event is sent to your subscribed endpoints, including your own. Ignore echoes of your own actions. See Webhooks.

Endpoint summary#

MethodPathPermission
GET/api/v1/conversationsconversations:read
GET/api/v1/messagesmessages:read
GET/api/v1/ordersorders:read
POST/api/v1/orders/actionorders:write

Good practice#

  • Store the key in a server-side secret manager and rotate it by creating a new key, switching your service, then revoking the old one.
  • Set an expiry on keys for contractors and pilots.
  • Log X-Sela-Request-Id with every failure.
  • Do not poll aggressively. For order changes, use webhooks and reconcile with GET /api/v1/orders?status=pending.
REST API | Sela docs | Sela