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 botId and bot-scoped API key. The metadata fields (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_id is 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 goal is "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. Kakapo also reads the newest messages from both sides, and the instruction, to search separately for each distinct thing the customer is asking, so a message with several questions is grounded for each of them. Agent messages help resolve references such as "the second option" but are never searched for themselves. The thread remains limited to 30 messages, with each message body limited to 12,000 characters. When the thread is longer than the model reads, the oldest text is dropped first.

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
    }
  ],
  "unanswered": ["Whether pets are allowed"],
  "usage": {
    "model": "gpt-4o-mini",
    "tokens": 1234
  },
  "failure_reason": null
}

unanswered lists what the customer asked that the bot's knowledge does not answer. The draft leaves those points out rather than guessing, so show the list to whoever reviews the draft before it is sent. It is always present, holds at most five short phrases in no guaranteed language, and is empty when everything was covered. Treat each entry as display text, not as a value to branch on.

Safe failure example:

{
  "reply": {
    "text": "",
    "confidence": 0
  },
  "grounding": [],
  "unanswered": [],
  "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

CodeStatusMeaningCaller action
API_KEY_INVALID401The 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_ALLOWED403The 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_MISMATCH403The 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_FOUND404The authenticated route bot does not exist or is archived.Ask an operator to verify the bot or create a new connection.
INVALID_BOT_ID400The route bot id is invalid.Fix the integration configuration before retrying.
INVALID_REQUEST_BODY400The reply-draft payload is invalid or has no customer message.Treat as a caller bug and fix the request.
RATE_LIMITED429The per-minute request limit for this bot and caller was reached.Retry with backoff after the rate-limit window.
USAGE_LIMIT_EXCEEDED402The 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_ERROR500Kakapo 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.