Kakapo Integrations API
Compose a grounded reply draft from your own system.
Integrations API
This page documents the server-to-server API that lets a third-party platform or product integrate with a Kakapo bot. For example, your own CRM can use it to generate a reply to a customer email from your company’s knowledge base.
It is for callers outside MindFull. MindFull Backoffice does not use it: its CRM reply drafting runs inside the platform and needs no key, so nothing on this page has to be set up for that to work. This API exists so that a system MindFull does not run can reach the same bot.
Authentication
Requests use a bot-scoped API key created under the bot's Configuration → Access in MindFull Backoffice. Create one for each outside system you connect:
Authorization: Bearer kakapo_live_<prefix>_<secret>
The full key is shown once at creation time and stored by Kakapo only as a hash. Keys are scoped to a single bot, carry explicit scopes, and can be revoked at any time from that same screen.
Common response envelope
Successful responses return:
{
"ok": true,
"data": {}
}
Errors return:
{
"ok": false,
"error": {
"code": "API_KEY_INVALID",
"message": "Error message",
"details": null
}
}
error.code is a stable contract value. Callers should branch on it instead of
message, which remains human-readable display copy.
GET /api/integrations/v1/bots/{botId}
Verifies that a bot id and API key can be used together without running retrieval, invoking a model, or recording billable usage.
- Required scope:
reply_draft. Verification deliberately accepts this existing scope so keys already issued for the reply-draft API continue to work without a migration or backfill. - Rate limit: 60 requests per minute, keyed by bot id and caller IP.
- Authorization boundary: the API key must belong to the route bot, and the API key owner must match the bot owner.
- Data returned: bot id, bot name, and the authenticated key's scopes only.
Response:
{
"ok": true,
"data": {
"bot": {
"id": "bot_123",
"name": "Support bot",
"scopes": ["reply_draft"]
}
}
}
POST /api/integrations/v1/bots/{botId}/reply-draft
Composes a grounded reply draft for a message or email thread. Kakapo only generates the suggested reply text; your application is responsible for reviewing it, sending it, and managing the conversation.
- Auth: Bot API key bearer token.
- Required scope:
reply_draft. - Rate limit: 30 requests per minute, keyed by bot id and caller IP, counted
before authentication or database work. Rate limits are operational and may be
revised; they are not part of the frozen
/v1/shape. - Retrieval scope: always filtered by
bot_id. - Authorization boundary: the route
botIdand bot-scoped API key. Themetadatafields (caller,crm_workspace_id,crm_thread_id,request_id) are optional audit context only and do not authorize access. The field names are historical;crm_workspace_idis whatever identifier your own system uses, and it is never read as a MindFull Workspace.
Request body:
{
"thread": {
"channel": "email",
"subject": "Question about pricing",
"messages": [
{
"role": "customer",
"from": "customer@example.com",
"sent_at": "2026-07-04T10:00:00Z",
"text": "Do you support annual billing?"
}
]
},
"reply_context": {
"tone": "professional",
"locale": "en",
"goal": "answer_customer",
"instruction": "Elaborate it.",
"previous_draft": "Hello Priya,\n\nYes, annual billing is available.\n\nKind regards,\nAcme Team"
},
"metadata": {
"caller": "acme-crm",
"crm_workspace_id": "workspace_123",
"crm_thread_id": "thread_456",
"request_id": "request_789"
}
}
reply_context is optional and has these fields:
tone: optional string, at most 80 characters. Defaults to"professional"when omitted or empty.locale: optional string, at most 20 characters. Defaults to"en"when omitted or empty.goal: optional enum,"answer_customer"or"follow_up". It defaults to"answer_customer"when omitted. For backward compatibility, any other string value is also normalized to"answer_customer"; strings over 120 characters remain invalid."answer_customer"drafts a response that addresses every customer message after the last agent message."follow_up"means the agent spoke last and the customer has not replied. The draft advances the conversation without repeating prior agent messages, or returns an empty draft with"nothing_to_add"when there is no useful follow-up.
instruction: optional operator steer, at most 500 characters. It may guide style, emphasis, or a revision, but cannot override grounding, non-invention, or insufficient-context rules.previous_draft: optional string, at most 4,000 characters, containing the your composer's current text verbatim. Its presence selects revision behavior; an explicitly empty string is still a revision request. There is no default.
When previous_draft is absent, behavior is unchanged: the route creates a
first draft and returns only the substantive body. The calling application owns
the greeting and sign-off.
When previous_draft is present, the route treats it as the text to edit and
returns the full revised message:
- With an
instruction, the route applies it while preserving wording and structure the instruction does not ask to change. - Without an
instruction, the route meaningfully improves clarity, completeness, and fit to the thread, tone, locale, and goal rather than echoing the draft unchanged. - If the draft has a greeting or sign-off but little or no body, the route drafts a grounded body and preserves that envelope.
- If
goalis"follow_up", the follow-up goal and revision behavior both apply. - Existing greetings and sign-offs are reproduced as given unless the instruction asks to change them. Missing greetings or sign-offs are not added.
Grounding, non-invention, and insufficient-context rules always take priority
over an instruction or text in previous_draft.
Retrieval uses the subject and every customer message after the last agent message. When the thread contains no agent message, it uses all customer messages in the supplied thread. When no customer message follows the last agent message, as expected for a follow-up, it falls back to all supplied customer messages so retrieval remains grounded in the conversation topic. The thread remains limited to 30 messages, with each message body limited to 12,000 characters.
The output contract depends on reply_context.previous_draft. When it is
absent, reply.text contains the substantive message body only. It never
intentionally includes a subject or Subject: prefix, greeting or salutation,
sign-off, closing, signature, team name, or company name. When
previous_draft is present, reply.text instead contains the full revised
message, including its supplied greeting and sign-off. The envelope is
preserved by default but may be changed by an explicit instruction.
Response data:
{
"reply": {
"text": "Yes, we support annual billing...",
"confidence": 0.82
},
"grounding": [
{
"source_id": "uuid",
"title": "Pricing FAQ",
"type": "qa",
"url": null
}
],
"usage": {
"model": "gpt-4o-mini",
"tokens": 1234
},
"failure_reason": null
}
Safe failure example:
{
"reply": {
"text": "",
"confidence": 0
},
"grounding": [],
"usage": {
"model": "gpt-4o-mini",
"tokens": 0
},
"failure_reason": "no_relevant_context"
}
failure_reason is a closed enum. Its complete set is:
null: no failure reason."no_relevant_context": Kakapo found no relevant bot-owned knowledge. This value is produced deterministically when retrieval returns no chunks and may also be returned by the model when retrieved context is insufficient."nothing_to_add": for a"follow_up"request, the customer has not replied and the model found no useful grounded follow-up that would avoid repeating the agent's prior message. This value is model-produced.
Any other model-produced value is discarded and returned as null.
Error codes
| Code | Status | Meaning | Caller action |
|---|---|---|---|
API_KEY_INVALID | 401 | The bearer key is missing, malformed, revoked, unknown, or does not belong to the route bot. | Reject the credential and ask an operator to issue a new key. |
API_KEY_SCOPE_NOT_ALLOWED | 403 | The key is valid for the bot but lacks the endpoint's required scope. | Keep the connection disabled until an operator supplies a key with reply_draft. |
API_KEY_BOT_OWNER_MISMATCH | 403 | The key and bot records do not have the same owner. Reserved: a key now belongs to a bot and the bot names its Workspace, so this is not returned today. | Reject the bot/key pairing and ask an operator to correct bot ownership. |
BOT_NOT_FOUND | 404 | The authenticated route bot does not exist or is archived. | Ask an operator to verify the bot or create a new connection. |
INVALID_BOT_ID | 400 | The route bot id is invalid. | Fix the integration configuration before retrying. |
INVALID_REQUEST_BODY | 400 | The reply-draft payload is invalid or has no customer message. | Treat as a caller bug and fix the request. |
RATE_LIMITED | 429 | The per-minute request limit for this bot and caller was reached. | Retry with backoff after the rate-limit window. |
USAGE_LIMIT_EXCEEDED | 402 | The bot owner's current plan usage allowance is exhausted. Reserved: no allowance is enforced yet, so this is not returned today. | Tell the operator to review Kakapo plan or usage; do not disable the credential. |
INTERNAL_ERROR | 500 | Kakapo could not validate internal state, load billing or usage, retrieve required data, or generate/parse a draft. | Keep the connection and retry later; escalate if the failure persists. |
Status alone is not sufficient to classify credential failures. A 401
indicates an invalid credential. A 403 indicates either a scope or owner
binding failure, distinguished by error.code. 402, 429, and 400 retain
the expected plan/usage, rate-limit, and caller-input meanings. 404 is a
missing/archived bot, while 500 and network failures mean the service is
temporarily unavailable and should not disable a stored connection.
Versioning
This API is versioned in the URL, currently /v1/. Integrations built against /v1/ will keep working unchanged; any breaking change will ship under a new version segment (e.g. /v2/) rather than altering /v1/ behavior.
That holds regardless of what MindFull’s own products do. These routes are a contract with callers outside MindFull, and they stay whether or not anything inside MindFull uses them.