Aiva over API reference
Endpoints, callback events, headers, and error codes for the Aiva over API REST surface.
Reference for the REST surface behind Aiva over API. All endpoints are relative to:
https://assistify.chat/api/v1
Responses use one envelope:
// success
{ "status": "success", "data": { /* … */ } }
// error
{ "status": "error", "statusCode": 402, "code": "INSUFFICIENT_CREDITS", "message": "…" }Authentication
Every request carries an API key as a Bearer token:
Authorization: Bearer ak_<keyId>_<secret>
Keys are created in the dashboard under Channels → Aiva over API → Keys. The full key is shown once at creation and only a hash is stored; revoking a key stops it on the next request.
Scopes
| Scope | Grants |
|---|---|
messages:write | POST /v1/messages |
identity:verify | POST /v1/identity/verify |
conversations:read | GET /v1/conversations/:id |
A call with a key missing the required scope fails with 403 FORBIDDEN.
Rate limits
120 requests per 60 seconds per key (default). Every response carries the current state in both header families:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per window |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | When the window resets (unix seconds) |
RateLimit-* | The same values in the IETF draft format |
Exceeding the limit returns 429 with Retry-After (seconds). Unauthenticated traffic is limited per IP before it reaches any per-key bucket.
Idempotency
POST /v1/messages requires an idempotencyKey (UUID):
- A retry with the same key returns the same
messageIdwithstatus: "duplicate", is never re-dispatched, and is never charged again. Concurrent duplicates collapse to one message. - Use a fresh UUID per logical message; reuse it only when retrying that exact message.
Endpoints
POST /v1/messages
Submit an end-user message. Scope: messages:write. Aiva processes it asynchronously; the reply arrives via callback or polling.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
externalConversationId | string (1–200) | yes | Your stable conversation id. One conversation per id. |
content | string (1–5000) | yes | The end-user's message text. |
idempotencyKey | string (UUID) | yes | Retry-safe anchor. |
externalUserId | string (1–200) | no | Your stable end-user id. Enables identity and per-user spend caps. |
channel is not accepted; it is forced to API server-side.
Response 202
{ "status": "success", "data": {
"conversationId": "…",
"messageId": "…",
"status": "queued" // "queued" | "duplicate" | "skipped" (+ "reason" when skipped)
}}Errors: 400 validation, 401 auth, 402 INSUFFICIENT_CREDITS, 403 scope/access, 429 USER_SPEND_CAP_REACHED, 429 rate limit.
POST /v1/identity/verify
Initiate an identity verification ceremony. Scope: identity:verify. The API can only initiate; verification is completed by the end-user out-of-band and can never be asserted by a key.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
externalUserId | string (1–200) | yes | The end-user to verify. |
method | "magic_link" | "oauth" | yes | oauth is not available here and returns status: "unsupported_here". |
email | string (email) | for magic_link | Where the single-use link is sent. |
Response 202
{ "status": "success", "data": { "method": "magic_link", "status": "sent" } }When the user completes the ceremony, an identity.verified event is pushed to every key that owns an API conversation for that user.
GET /v1/conversations/:externalConversationId
Read conversation state and message history. Scope: conversations:read. Use it to poll for replies when no callback is configured.
Response 200
{ "status": "success", "data": {
"externalConversationId": "order-4821",
"conversationId": "…",
"status": "OPEN",
"createdAt": "…",
"lastMessageAt": "…",
"messages": [
{ "id": "…", "seq": 1, "senderType": "VISITOR", "content": "…", "createdAt": "…" },
{ "id": "…", "seq": 2, "senderType": "AI", "content": "…", "createdAt": "…" }
]
}}Text messages only, in seq order; internal notes and system events are excluded. Unknown id returns 404 NOT_FOUND.
Callback events
Configured per key (an https callbackUrl set at creation). Each delivery is an HTTP POST with these headers:
| Header | Meaning |
|---|---|
X-Assistify-Event | Event name |
X-Assistify-Signature | sha256=<hex> HMAC of `${timestamp}.${rawBody}` with the key's callback secret |
X-Assistify-Timestamp | Unix seconds, signed into the HMAC |
X-Assistify-Delivery-Id | Stable per-delivery UUID, reused across retries. Deduplicate on it. |
Events
| Event | When | Body fields |
|---|---|---|
ai.reply | Aiva generated a reply | event, conversationId, messageId, seq, content, visibility, poweredBy, timestamp |
agent.reply | A human agent replied from the dashboard | event, conversationId, messageId, seq, content, agentName, timestamp |
conversation.handoff | The conversation escalated to a human | event, conversationId, reason, summary, nextStep, isUrgent, timestamp |
identity.verified | An end-user completed verification | event, externalUserId, tier: "verified", timestamp |
test.ping | You pressed Send test on a key | event, message, keyId, timestamp |
conversationId in callback bodies is always your externalConversationId. visibility on ai.reply is "public" or "private" (private may reference personal data of a verified user; render it only behind that user's authentication).
Delivery semantics
- Respond 2xx within 5 seconds; anything else counts as a failure.
- Failures are retried with exponential backoff (5 attempts). Delivery is degraded, never disabled: a broken endpoint does not switch off the channel, and exhausted deliveries stay visible in the dashboard Logs tab, where they can be replayed.
- A replay from the Logs tab re-sends the original payload with a new delivery id.
- Callback URLs must be https and publicly resolvable. URLs resolving to private or loopback address space are blocked at delivery time.
Identity model
| Tier | Meaning |
|---|---|
| anonymous | No externalUserId supplied; each message creates or reuses an anonymous contact. |
| unverified | externalUserId supplied; asserted by the key, does not unlock personal tools. |
| verified | The end-user completed a verification ceremony; personal and account-scoped tools unlock. |
Personal-data tools are gated server-side on verified identity; a key cannot forge it.
Billing
Usage is billed per token from a prepaid credit wallet (rate and balance under Channels → Aiva over API → Credits). Each request is gated before generation (insufficient balance fails closed with 402, nothing is charged) and settled once after generation. Idempotent retries and redeliveries never double-charge. Optional rolling per-end-user spend caps return 429 USER_SPEND_CAP_REACHED when reached.
Errors
| HTTP | code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Bad or missing body field (for example a non-UUID idempotencyKey). |
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key. |
| 402 | INSUFFICIENT_CREDITS | Wallet cannot cover a worst-case generation. Top up. |
| 403 | FORBIDDEN | Key lacks the required scope. |
| 403 | API_NOT_ENABLED | The public API is not available right now. |
| 403 | API_ACCESS_REQUIRED | Your workspace has no active API access. Request it in the dashboard. |
| 403 | API_ACCESS_REVOKED | Your workspace's API access was revoked. |
| 404 | NOT_FOUND | Unknown externalConversationId. |
| 429 | USER_SPEND_CAP_REACHED | The end-user's rolling spend cap is reached. |
| 429 | (rate limit) | Per-key or per-IP rate limit. Honor Retry-After. |