Walker Formulation Academy for developers API reference Error catalog OpenAPI
Sections

Conversations

Methods in this section: 5. All methods

MethodPathDescriptionScopesStatus
GET/conversationsList conversationsconversations:readAvailable PII
POST/conversationsFind or create a conversationconversations:writeAvailable PII
GET/conversations/{id}Get a conversationconversations:readAvailable PII
GET/conversations/{id}/messagesConversation messagesconversations:readAvailable PII
POST/conversations/{id}/messagesCustomer message and AI replyconversations:writeAvailable PII
GET /conversations Available PII

#List conversations

Tenant conversations in ascending updated_at order, paged by cursor: to pick up changes, keep the updated_at of the last one you saw and pass it as updated_after. Conversations have no soft deletion, so include_deleted adds nothing for now. The response contains customer personal data: every disclosure is recorded in the disclosure log; on RU an IP allowlist and recipient country RU are required (otherwise 403 pii_transfer_not_allowed). Personal-mailbox conversations and student-portal chats are not returned. Personal-data fields (external_customer_id of a conversation; content and attachments of a message) are returned only when named in fields=. On the EN and ID cells their values may come in JWE format (the integration mode and the Encryption-Key-Id header); in mode required without a key — 409 encryption_key_required.

Scopes
conversations:read
Rate limit
read
Idempotency
not required
Successful response
200 A page of conversations.

Parameters

FieldTypeDescription
limitinteger
optional
In: query. Page size: 1 to 100, default 25.
cursorstring
optional
In: query. The next_cursor value from the previous response. Valid for 24 hours; start over when filters change.
channelstring
optional
In: query. Channel type: api, telegram, website …
statusstring
optional
In: query. Conversation state, for example open.
modestring
optional
In: query. ai — handled by the AI, operator — handled by a human.
updated_afterstring
optional
In: query. Conversations changed strictly after the moment. ISO 8601 with a zone.
include_deletedboolean
optional
In: query. Accepted for uniformity; changes nothing for now.
fieldsstring
optional
In: query. Comma-separated fields to return (id is always present). Without the parameter every field EXCEPT personal-data fields is returned; a personal-data field is returned only when named explicitly: top-level (title, content) or nested (contact.email, score.reason). An unknown name — 422. Only the returned fields are recorded in the disclosure log.
Encryption-Key-Idstring
optional
In: header. EN and ID cells only: which registered key (kid) to build the JWE with — the active one or the previous one for 7 days after a change. The header enables the JWE format even in mode off.

Response body

FieldTypeDescription
dataarray<Conversation>
required
next_cursorstring
required, may be null
Cursor of the next page; null — the list is exhausted. Valid for 24 hours.

Examples

Request
curl -X GET 'https://wfacademy.id/api/public/v1/conversations' \
  -H 'Authorization: Bearer '"$API_KEY"
Response 200
{
  "data": [
    {
      "id": "EXAMPLE_ID",
      "channel": null,
      "contact_id": null,
      "external_customer_id": null,
      "status": "string",
      "mode": "ai",
      "language": null,
      "created_at": null,
      "updated_at": "2026-10-11T09:00:00Z",
      "_enc": [
        null
      ]
    }
  ],
  "next_cursor": "EXAMPLE_CURSOR"
}
Error example
{
  "type": "https://wfacademy.id/developers/errors#scope_missing",
  "title": "Scope is missing",
  "status": 403,
  "code": "scope_missing",
  "detail": "EXAMPLE",
  "request_id": "req_EXAMPLE"
}

Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.

Possible errors

invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed validation_failed encryption_key_required

POST /conversations Available PII

#Find or create a conversation

A conversation of your channel customer (the integration api channel) by the external id: an active one is returned (200), otherwise a new one is created (201) with a conversation.created event. The same external_customer_id of two integrations means two different customers. Name, email and phone are written into empty contact fields only. Connect the channel in the dashboard (Settings → API); without it — 409 channel_not_configured. A retry with the same Idempotency-Key returns the same conversation. The response contains customer personal data: every disclosure is recorded in the disclosure log; on RU an IP allowlist and recipient country RU are required (otherwise 403 pii_transfer_not_allowed). Personal-mailbox conversations and student-portal chats are not returned.

Scopes
conversations:write
Rate limit
write
Idempotency
Idempotency-Key is required
Successful response
201 The conversation (new — 201, existing — 200).

Parameters

FieldTypeDescription
Idempotency-Keystring
required
In: header. A unique string (usually a UUID v4), new for every new operation. A retry with the same key and body yields the same result.

Request body Your channel customer.

FieldTypeDescription
external_customer_idstring
required
A stable customer id in your system (not a phone or email). The platform finds or creates the contact and the conversation by it; ids of different integrations never mix.
namestring
optional
Customer name. Written only into an empty contact field.
emailstring
optional
Customer email. Written only into an empty contact field.
phonestring
optional
Phone in international format (+…). Written only into an empty contact field.
languagestring
optional
Language of a new conversation (ISO 639-1). The AI reply language is limited to the cell languages.

Response body

FieldTypeDescription
idstring
required
Conversation id (uuid).
channelstring
required, may be null
Channel type: api, telegram, website …
contact_idstring
required, may be null
The platform contact (customer) id.
external_customer_idstring
optional, may be null
The customer id in your system. Channel api only; null for other channels (the messenger external id is not disclosed). Personal data: on reads it is returned only when named in fields=. On the EN and ID cells in JWE format — a compact JWE string.
statusstring
required
Conversation state: open, handoff_pending, resolved, closed … The set may grow — do not treat the list as closed.
modestring
required
Who handles the conversation: ai — the AI answers (modes auto and supervised), operator — a human answers (copilot and manual).
Values: ai operator
languagestring
required, may be null
Conversation language (ISO 639-1); null — not detected.
created_atstring
required, may be null
Conversation start, ISO 8601 UTC.
updated_atstring
required
Last change or message, ISO 8601 UTC. The walk order. The unread counter does not change it.
_encarray<string>
optional
EN and ID cells only, when the JWE format is in effect: fields of this object whose values are replaced by a compact JWE string (the plaintext is the JSON of the original value).

Examples

Request
curl -X POST 'https://wfacademy.id/api/public/v1/conversations' \
  -H 'Authorization: Bearer '"$API_KEY" \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "external_customer_id": "EXAMPLE_ID",
  "name": "string",
  "email": "[email protected]",
  "phone": "string",
  "language": "string"
}'
Response 201
{
  "id": "EXAMPLE_ID",
  "channel": "string",
  "contact_id": "EXAMPLE_ID",
  "external_customer_id": "EXAMPLE_ID",
  "status": "string",
  "mode": "ai",
  "language": "string",
  "created_at": "2026-10-11T09:00:00Z",
  "updated_at": "2026-10-11T09:00:00Z",
  "_enc": [
    "string"
  ]
}
Error example
{
  "type": "https://wfacademy.id/developers/errors#scope_missing",
  "title": "Scope is missing",
  "status": 403,
  "code": "scope_missing",
  "detail": "EXAMPLE",
  "request_id": "req_EXAMPLE"
}

Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.

Possible errors

invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed validation_failed bad_request payload_too_large channel_not_configured idempotency_key_required idempotency_conflict operation_in_progress invalid_state

GET /conversations/{id} Available PII

#Get a conversation

One conversation. A foreign, missing or non-public conversation all answer not_found alike. The response contains customer personal data: every disclosure is recorded in the disclosure log; on RU an IP allowlist and recipient country RU are required (otherwise 403 pii_transfer_not_allowed). Personal-mailbox conversations and student-portal chats are not returned. Personal-data fields (external_customer_id of a conversation; content and attachments of a message) are returned only when named in fields=. On the EN and ID cells their values may come in JWE format (the integration mode and the Encryption-Key-Id header); in mode required without a key — 409 encryption_key_required.

Scopes
conversations:read
Rate limit
read
Idempotency
not required
Successful response
200 The conversation.

Parameters

FieldTypeDescription
idstring
required
In: path. Conversation id (uuid).
fieldsstring
optional
In: query. Comma-separated fields to return (id is always present). Without the parameter every field EXCEPT personal-data fields is returned; a personal-data field is returned only when named explicitly: top-level (title, content) or nested (contact.email, score.reason). An unknown name — 422. Only the returned fields are recorded in the disclosure log.
Encryption-Key-Idstring
optional
In: header. EN and ID cells only: which registered key (kid) to build the JWE with — the active one or the previous one for 7 days after a change. The header enables the JWE format even in mode off.

Response body

FieldTypeDescription
idstring
required
Conversation id (uuid).
channelstring
required, may be null
Channel type: api, telegram, website …
contact_idstring
required, may be null
The platform contact (customer) id.
external_customer_idstring
optional, may be null
The customer id in your system. Channel api only; null for other channels (the messenger external id is not disclosed). Personal data: on reads it is returned only when named in fields=. On the EN and ID cells in JWE format — a compact JWE string.
statusstring
required
Conversation state: open, handoff_pending, resolved, closed … The set may grow — do not treat the list as closed.
modestring
required
Who handles the conversation: ai — the AI answers (modes auto and supervised), operator — a human answers (copilot and manual).
Values: ai operator
languagestring
required, may be null
Conversation language (ISO 639-1); null — not detected.
created_atstring
required, may be null
Conversation start, ISO 8601 UTC.
updated_atstring
required
Last change or message, ISO 8601 UTC. The walk order. The unread counter does not change it.
_encarray<string>
optional
EN and ID cells only, when the JWE format is in effect: fields of this object whose values are replaced by a compact JWE string (the plaintext is the JSON of the original value).

Examples

Request
curl -X GET 'https://wfacademy.id/api/public/v1/conversations/EXAMPLE_ID' \
  -H 'Authorization: Bearer '"$API_KEY"
Response 200
{
  "id": "EXAMPLE_ID",
  "channel": "string",
  "contact_id": "EXAMPLE_ID",
  "external_customer_id": "EXAMPLE_ID",
  "status": "string",
  "mode": "ai",
  "language": "string",
  "created_at": "2026-10-11T09:00:00Z",
  "updated_at": "2026-10-11T09:00:00Z",
  "_enc": [
    "string"
  ]
}
Error example
{
  "type": "https://wfacademy.id/developers/errors#scope_missing",
  "title": "Scope is missing",
  "status": 403,
  "code": "scope_missing",
  "detail": "EXAMPLE",
  "request_id": "req_EXAMPLE"
}

Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.

Possible errors

invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed not_found validation_failed encryption_key_required

GET /conversations/{id}/messages Available PII

#Conversation messages

Conversation messages from oldest to newest, paged by cursor (created_at, id). Operator internal notes are not returned. Attachments are temporary signed links; external messenger file addresses are not disclosed. The response contains customer personal data: every disclosure is recorded in the disclosure log; on RU an IP allowlist and recipient country RU are required (otherwise 403 pii_transfer_not_allowed). Personal-mailbox conversations and student-portal chats are not returned. Personal-data fields (external_customer_id of a conversation; content and attachments of a message) are returned only when named in fields=. On the EN and ID cells their values may come in JWE format (the integration mode and the Encryption-Key-Id header); in mode required without a key — 409 encryption_key_required.

Scopes
conversations:read
Rate limit
read
Idempotency
not required
Successful response
200 A page of messages.

Parameters

FieldTypeDescription
idstring
required
In: path. Conversation id (uuid).
limitinteger
optional
In: query. Page size: 1 to 100, default 25.
cursorstring
optional
In: query. The next_cursor value from the previous response. Valid for 24 hours; start over when filters change.
include_deletedboolean
optional
In: query. true — also return deleted messages as tombstones (deleted: true, no text).
fieldsstring
optional
In: query. Comma-separated fields to return (id is always present). Without the parameter every field EXCEPT personal-data fields is returned; a personal-data field is returned only when named explicitly: top-level (title, content) or nested (contact.email, score.reason). An unknown name — 422. Only the returned fields are recorded in the disclosure log.
Encryption-Key-Idstring
optional
In: header. EN and ID cells only: which registered key (kid) to build the JWE with — the active one or the previous one for 7 days after a change. The header enables the JWE format even in mode off.

Response body

FieldTypeDescription
dataarray<Message>
required
next_cursorstring
required, may be null
Cursor of the next page; null — the list is exhausted. Valid for 24 hours.

Examples

Request
curl -X GET 'https://wfacademy.id/api/public/v1/conversations/EXAMPLE_ID/messages' \
  -H 'Authorization: Bearer '"$API_KEY"
Response 200
{
  "data": [
    {
      "id": "EXAMPLE_ID",
      "conversation_id": "EXAMPLE_ID",
      "sender": "customer",
      "content_type": "string",
      "content": null,
      "attachments": null,
      "cards": [
        null
      ],
      "deleted": false,
      "created_at": "2026-10-11T09:00:00Z",
      "_enc": [
        null
      ]
    }
  ],
  "next_cursor": "EXAMPLE_CURSOR"
}
Error example
{
  "type": "https://wfacademy.id/developers/errors#scope_missing",
  "title": "Scope is missing",
  "status": 403,
  "code": "scope_missing",
  "detail": "EXAMPLE",
  "request_id": "req_EXAMPLE"
}

Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.

Possible errors

invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed not_found validation_failed encryption_key_required

POST /conversations/{id}/messages Available PII

#Customer message and AI reply

Saves a customer message into a conversation of the integration channel (conversations:write) and, with ai_reply=auto and the ai_seller:invoke scope, starts the AI seller reply asynchronously: 202 with message_id, ai_reply.status=queued and a conversation.ai_reply operation. ?wait=N (0–20 s) waits for the SAME job: in time — 200 with the result (ai_reply, reply.id, operation.usage.cost in the wallet currency), otherwise — 202. Without ai_seller:invoke or with ai_reply=none — 201, ai_reply.status=skipped, reason=not_requested; the AI is not called and nothing is charged. The AI reply text is not here — read GET /conversations/{id}/messages or wait for message.created. A retry with the same Idempotency-Key while running returns the same 202, afterwards the result; the message is never written or charged twice. Paid part: at most 5 concurrent operations per integration (429 concurrency_limit), the per-operation cost cap and the integration daily limit (402 spend_limit_reached) are checked before writing; a refusal writes nothing and frees the Idempotency-Key. A conversation of another channel or tenant — 404, a closed one — 409 invalid_state.

Scopes
conversations:write
Rate limit
write
Idempotency
Idempotency-Key is required
Successful response
202 Accepted; the AI reply is being prepared (202), ready within wait (200) or not requested (201).

Parameters

FieldTypeDescription
idstring
required
In: path. Conversation id (uuid).
Idempotency-Keystring
required
In: header. A unique string (usually a UUID v4), new for every new operation. A retry with the same key and body yields the same result.
waitinteger
optional
In: query. Seconds to wait for the AI reply, 0–20. Not part of the idempotency key.

Request body The customer message.

FieldTypeDescription
contentstring
required
The customer message text, up to 4000 characters.
ai_replystring
required
auto — request an AI seller reply (needs the ai_seller:invoke scope; without it the message is only saved); none — only save the message.
Values: auto none

Response body

FieldTypeDescription
message_idstring
required
The id of the saved customer message.
conversation_idstring
required
The conversation.
ai_replyAiReply
required
replyobject
optional
replied only: the AI reply id. No text here — read it with GET /conversations/{id}/messages (the disclosure is logged).
reply.idstring
required
operationOperation
optional
The conversation.ai_reply operation: queued or running with 202, finished with 200 (usage.cost — charged to the wallet in its currency). Absent when no AI reply was requested.

Examples

Request
curl -X POST 'https://wfacademy.id/api/public/v1/conversations/EXAMPLE_ID/messages' \
  -H 'Authorization: Bearer '"$API_KEY" \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "content": "string",
  "ai_reply": "auto"
}'
Response 202
{
  "message_id": "EXAMPLE_ID",
  "conversation_id": "EXAMPLE_ID",
  "ai_reply": {
    "status": "replied",
    "reason": "operator_active"
  },
  "reply": {
    "id": "EXAMPLE_ID"
  },
  "operation": {
    "id": "EXAMPLE_ID",
    "type": "string",
    "status": "queued",
    "created_at": "2026-10-11T09:00:00Z",
    "completed_at": "2026-10-11T09:00:00Z",
    "resource": {
      "type": "string",
      "id": "EXAMPLE_ID"
    },
    "error": {
      "code": "string",
      "message": "string"
    },
    "usage": {
      "cost": null,
      "model": null
    }
  }
}
Error example
{
  "type": "https://wfacademy.id/developers/errors#scope_missing",
  "title": "Scope is missing",
  "status": 403,
  "code": "scope_missing",
  "detail": "EXAMPLE",
  "request_id": "req_EXAMPLE"
}

Examples are illustrative: the key in them is a placeholder (EXAMPLE), field values are made up.

Possible errors

invalid_api_key wrong_cell integration_inactive tenant_inactive ip_not_allowed feature_not_in_plan rate_limited wallet_suspended service_unavailable internal_error scope_missing pii_transfer_not_allowed validation_failed bad_request payload_too_large not_found channel_not_configured invalid_state idempotency_key_required idempotency_conflict operation_in_progress concurrency_limit spend_limit_reached upstream_unavailable