Kamai Messages API

Send a message; get the answer as a stream of events or as one JSON reply.

GitHub OpenAPI

Operations 1

POST /v1/conversations/{conversation_id}/messages Send a message and get the answer #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/kamai-io:kamai-io-messages-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

kamai-io-messages-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Kamai Messages API
  description: 'Read construction drawings from your own software. Upload a PDF plan; Kamai reads it and gives you back every room, wall,

    door and window with measurements in metres, every printed word, and every printed table (schedules, legends, revision

    blocks), as JSON. ## Authentication


    Create an API key at Kamai → Settings → API keys and send it on every request:


    ```

    Authorization: Bearer

    ```


    A key acts as the user who created it and sees only that user''s projects. Keep it on your server. A call without a key,

    or with a revoked one, gets `401`. Revoking a key, or a change Kamai makes to its settings, takes effect within 3 minutes. ## Acting for your users


    A partner whose users sign in through it can use any API key to act for those users. The request carries, next to the

    key, `X-Kamai-Partner` (your partner ID, as Kamai gave it to you) and `X-Kamai-Partner-User-Token` (that user''s own token

    from you). Kamai checks the token with you and acts as that user: that user''s projects, that user''s page quota, as if the

    user did it in Kamai. Without the two headers the key acts as its own owner. One header without the other is `400`

    (`partner_id_required`, `partner_user_token_required`). A user who does not exist in Kamai or is disabled gets `403`

    (`act_as_not_allowed`). A key Kamai has tied to one partner always needs both headers and never acts as its owner. What the key may do for a user is set by Kamai on the key:


    | | routes | needs |

    |---|---|---|

    | read | every `GET`, and `POST /v1/blueprints/{blueprint_id}/markups/clip` | the key enabled for partner users |

    | change | create and update projects, upload, update sheets, create and update markups, rename conversations | edit enabled |

    | delete | delete projects, jobs, markups and conversations, cancel jobs | delete enabled, separately from edit |


    A route the key may not use gets `403` (`edit_not_allowed`, `delete_not_allowed`). On every key, not only for partner users, renaming a conversation needs edit enabled and deleting one needs delete enabled; a new key has both off. Replacing a sheet''s features with

    `PUT /v1/blueprints/{blueprint_id}` for a user needs the `revision` you read as `base_revision` (`422`

    `base_revision_required`), and replacing them with none counts as a delete. Conversations work the same way;

    with edit enabled for the key, the assistant may change the user''s drawings and undo that user''s own changes; keys

    without it never write. Uploads count against the user''s own page quota. Kamai keeps the answer of each token check for up to 60 seconds, so a user you disable on your side can still be served

    for that long. ## The usual flow


    1. `POST /v1/blueprints/upload/init` returns a signed URL. 2. `PUT` the PDF to that URL. 3. `POST /v1/blueprints/upload/complete` returns a `job_id`. It starts one job per upload: calling it again with the

    same `file_uuid` returns the same `job_id` (`409` with `Retry-After` while the first call is still running). To send

    another file, start a new upload with `init`. 4. Poll `GET /v1/projects/{project_id}/jobs/{job_id}` every 5–10 s until `status` is final: `SUCCEEDED`, or

    `FAILED` / `CANCELLED` / `ABANDONED`, which end the job; for those, show `error_code` and `error_message`. The PDF check fails a job with `pdf_incomplete` (the file looks cut off or damaged: download or export it again),

    `pdf_filter_unsupported` (Brotli compression, or a PDF that cannot be read) or `pdf_scan_over_budget` (too large or

    complex to check). New `error_code` values can be added over time: treat one you do not know as a failure and show

    its `error_message`. The job record is written when processing starts, not when the upload returns, and when many uploads are queued that

    can take minutes. Until then, a `job_id` you got from an upload answers `404` with `detail` `Job not found`: keep

    polling it. `Project not found` is final. 5. On `SUCCEEDED`, read the sheet with `GET /v1/blueprints/{blueprint_id}`.'
  version: 0.1.0
tags:
- name: Messages
  description: Send a message; get the answer as a stream of events or as one JSON reply.
paths:
  /v1/conversations/{conversation_id}/messages:
    post:
      tags:
      - Messages
      summary: Send a message and get the answer
      description: 'Send one message; the assistant answers it with the conversation''s history in mind.


        **`Accept: text/event-stream`**: the answer streams as Server-Sent Events, one frame per event

        (`event: `, `id: `, `data: {"type", "seq", "data"}`): `turn.accepted`, `turn.started`, `phase`, `activity` (what the assistant is doing, `{"kind", "label"}`; kinds: reading, finding, counting, measuring, catalogue, bill_of_quantities, combining, editing, help, working), `text.message` (the full answer text so far; replaces what the deltas built),

        `text.delta`, `table`, `highlights`, `question`, `features.changed`, then `turn.done` (the full Turn, its

        `answer` authoritative) or `turn.failed`. A `ping` comes after 15 s of silence. If you disconnect, the turn

        still finishes and lands in the conversation.


        **`Accept: application/json`** (default): the call returns the finished Turn, waiting up to 600 s; after that

        504 `turn_timeout` with the `turn_id` (the turn keeps running and lands in the conversation).


        One turn at a time per conversation (409 `conversation_busy`); at most 2 turns at once per API key by default (Kamai can set more on a key) and a daily

        number of turns per key, UTC day (429 `rate_limited` with `Retry-After`). Each message counts as one chat turn.'
      operationId: send_message_v1_conversations__conversation_id__messages_post
      security:
      - HTTPBearer: []
      parameters:
      - name: conversation_id
        in: path
        required: true
        schema:
          type: string
          title: Conversation Id
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          anyOf:
          - type: string
            maxLength: 64
          - type: 'null'
          description: 'A retry with the same key follows the turn the first request started instead of starting another: the same stream or Turn. If that turn can no longer be followed, 409 with its `turn_id` (`conversation_busy` while it runs, `already_sent` once it ended; its answer is in the conversation).'
          title: Idempotency-Key
        description: 'A retry with the same key follows the turn the first request started instead of starting another: the same stream or Turn. If that turn can no longer be followed, 409 with its `turn_id` (`conversation_busy` while it runs, `already_sent` once it ended; its answer is in the conversation).'
      - name: X-Kamai-Partner-User-Token
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: 'For partners whose users sign in through them: the partner''s own token for the end user. Kamai verifies it with the partner and acts as that user. Without it the call acts as the API key''s owner.'
          title: X-Kamai-Partner-User-Token
        description: 'For partners whose users sign in through them: the partner''s own token for the end user. Kamai verifies it with the partner and acts as that user. Without it the call acts as the API key''s owner.'
      - name: X-Kamai-Partner
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: 'Your partner ID, as Kamai gave it to you: the partner that issued `X-Kamai-Partner-User-Token`. Required with it.'
          title: X-Kamai-Partner
        description: 'Your partner ID, as Kamai gave it to you: the partner that issued `X-Kamai-Partner-User-Token`. Required with it.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageCreate'
            examples:
              question:
                summary: A question
                value:
                  text: How many doors are on this sheet, by type?
                  language: en
              mention:
                summary: About one element
                value:
                  text: What is the opening width of this door?
                  mentions:
                  - type: feature
                    id: dddddddd-0000-4000-8000-000000000001
                    label: D1
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Turn'
              examples:
                real:
                  summary: Finished turn
                  description: Shape built from the chat service's own payloads (not yet captured from a live call). Ids and text are illustrative.
                  value:
                    id: cccc0000000000000000000000000001
                    conversation_id: cv1_WyI0NTY3ODkwMTIzNDU2Nzg5MDEyIiwiYWFhYTAwMDAwMDAzIiwiYmJiYmJiYmItMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwicmVhZF9vbmx5Il0
                    status: done
                    created_at: '2026-10-18T07:12:21Z'
                    finished_at: '2026-10-18T07:12:44Z'
                    answer:
                      text: 'There are 3 doors on this sheet:


                        {{table:0}}


                        Most are D1 (single leaf, 90 cm).'
                      tables:
                      - index: 0
                        title: Doors by type
                        columns:
                        - key: type
                          label: Type
                          type: text
                          unit: null
                        - key: count
                          label: Count
                          type: number
                          unit: pcs
                        rows:
                        - cells:
                            type: D1
                            count: 2
                          feature_ids:
                          - dddddddd-0000-4000-8000-000000000001
                          - dddddddd-0000-4000-8000-000000000002
                          text_ids: []
                        - cells:
                            type: D2
                            count: 1
                          feature_ids:
                          - dddddddd-0000-4000-8000-000000000003
                          text_ids: []
                      boq: null
                      highlights:
                      - blueprint_id: bbbbbbbb-0000-4000-8000-000000000001
                        feature_ids:
                        - dddddddd-0000-4000-8000-000000000001
                        - dddddddd-0000-4000-8000-000000000002
                        - dddddddd-0000-4000-8000-000000000003
                        text_ids: []
                      question: null
                      question_options: []
                      changed_blueprint_ids: []
                      degraded: false
                    error: null
                    error_message: null
                    usage:
                      duration_ms: 23140
            text/event-stream:
              schema:
                type: string
              example: 'event: turn.accepted

                id: 1

                data: {"type": "turn.accepted", "seq": 1, "data": {"turn_id": "cccc0000000000000000000000000001", "conversation_id": "cv1_..."}}


                event: turn.started

                id: 2

                data: {"type": "turn.started", "seq": 2, "data": {}}


                event: text.delta

                id: 9

                data: {"type": "text.delta", "seq": 9, "data": {"text": "There are 3 doors"}}


                event: ping

                data: {"type": "ping", "seq": 9, "data": {}}


                event: turn.done

                id: 14

                data: {"type": "turn.done", "seq": 14, "data": {"id": "cccc0000000000000000000000000001", "status": "done", "answer": {"text": "..."}}}


                '
        '400':
          description: 'On a key enabled for partner users: `X-Kamai-Partner` missing (`partner_id_required`) or not valid for this key (`partner_id_invalid`), or `X-Kamai-Partner-User-Token` missing (`partner_user_token_required`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '401':
          description: No API key, a revoked key (`unauthorized`), or a partner user token the partner rejected (`partner_token_invalid`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '403':
          description: The key may not use `tools=edit` or rename a conversation (`edit_not_allowed`), may not delete a conversation (`delete_not_allowed`), may not act for partner users (`act_as_not_allowed`), or the partner user's account is not linked to Kamai (`partner_user_not_linked`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '404':
          description: Project, sheet or conversation not found for the acting user (`not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '409':
          description: A turn is still running in this conversation (`conversation_busy`), or a retry's turn already ended (`already_sent`); `turn_id` names it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '413':
          description: The sheet or project is too large for one turn (`context_too_large`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '429':
          description: 'Too many turns (`rate_limited`): 2 at once per key, or the key''s daily turns (UTC day). See `Retry-After`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '502':
          description: The assistant or the partner failed to answer (`upstream_error`, `partner_unavailable`). Safe to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '503':
          description: Chat is not available on this deployment (`chat_unavailable`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '504':
          description: The turn did not finish within 600 s (`turn_timeout`). It keeps running and lands in the conversation; `turn_id` names it. Retry with the same `Idempotency-Key` to wait for it again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api__external__v1__conversations__ErrorResponse'
        '422':
          description: 'A field is invalid: `detail` lists which, or carries `invalid_request`.'
components:
  schemas:
    MessageCreate:
      properties:
        text:
          type: string
          maxLength: 4000
          minLength: 1
          title: Text
          description: The message, as a user would type it in the Kamai chat.
        visible_feature_ids:
          items:
            type: string
          type: array
          maxItems: 2000
          title: Visible Feature Ids
          description: Elements the user is looking at.
        sheet_numbers:
          anyOf:
          - additionalProperties:
              type: integer
            type: object
          - type: 'null'
          title: Sheet Numbers
          description: blueprint_id → the number the user sees on that sheet in a project grid.
        mentions:
          items:
            $ref: '#/components/schemas/Mention'
          type: array
          maxItems: 50
          title: Mentions
        attachments:
          items:
            $ref: '#/components/schemas/Attachment'
          type: array
          maxItems: 4
          title: Attachments
        language:
          anyOf:
          - type: string
            maxLength: 16
          - type: 'null'
          title: Language
          description: Answer language, e.g. `en`, `he`, `fr`.
      type: object
      required:
      - text
      title: MessageCreate
    Turn:
      properties:
        id:
          type: string
          title: Id
          description: 'The turn id: rate it with POST .../turns/{turn_id}/feedback.'
        conversation_id:
          type: string
          title: Conversation Id
        status:
          type: string
          enum:
          - queued
          - waiting_previous_turn
          - running
          - done
          - failed
          - cancelled
          title: Status
        created_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Created At
        finished_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Finished At
        answer:
          anyOf:
          - $ref: '#/components/schemas/Answer'
          - type: 'null'
        error:
          anyOf:
          - type: string
          - type: 'null'
          title: Error
        error_message:
          anyOf:
          - type: string
          - type: 'null'
          title: Error Message
        usage:
          $ref: '#/components/schemas/TurnUsage'
      type: object
      required:
      - id
      - conversation_id
      - status
      - usage
      title: Turn
    Mention:
      properties:
        type:
          type: string
          enum:
          - feature
          - blueprint
          - saved_table
          title: Type
        id:
          type: string
          minLength: 1
          title: Id
        blueprint_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Blueprint Id
          description: The feature's sheet. Needed for `feature` in a project conversation.
        label:
          anyOf:
          - type: string
            maxLength: 200
          - type: 'null'
          title: Label
          description: How the user named it, e.g. `A-101`.
      type: object
      required:
      - type
      - id
      title: Mention
    Table:
      properties:
        index:
          type: integer
          title: Index
          description: 'Its place in the answer: `{{table:N}}` in `text`.'
        title:
          anyOf:
          - type: string
          - type: 'null'
          title: Title
        columns:
          items: {}
          type: array
          title: Columns
          description: '`key`, `label`, `type`, `unit` per column.'
        rows:
          items:
            $ref: '#/components/schemas/TableRow'
          type: array
          title: Rows
      type: object
      required:
      - index
      title: Table
    TurnUsage:
      properties:
        duration_ms:
          type: integer
          title: Duration Ms
      type: object
      required:
      - duration_ms
      title: TurnUsage
    TableRow:
      properties:
        cells:
          title: Cells
          description: The row's values, keyed by column `key`.
        feature_ids:
          items:
            type: string
          type: array
          title: Feature Ids
          description: The drawing elements this row counts, as in GET /v1/blueprints/{blueprint_id}.
        text_ids:
          items:
            type: string
          type: array
          title: Text Ids
          description: The printed words the row was read from.
      type: object
      title: TableRow
    Highlight:
      properties:
        blueprint_id:
          type: string
          title: Blueprint Id
        feature_ids:
          items:
            type: string
          type: array
          title: Feature Ids
        text_ids:
          items:
            type: string
          type: array
          title: Text Ids
      type: object
      required:
      - blueprint_id
      title: Highlight
    Answer:
      properties:
        text:
          type: string
          title: Text
          description: The answer. Each table stands where `{{table:N}}` is.
          default: ''
        tables:
          items:
            $ref: '#/components/schemas/Table'
          type: array
          title: Tables
        boq:
          anyOf:
          - additionalProperties: true
            type: object
          - type: 'null'
          title: Boq
          description: A structured bill of quantities, when the turn produced one.
        highlights:
          items:
            $ref: '#/components/schemas/Highlight'
          type: array
          title: Highlights
          description: Elements the answer points at, per sheet (what the app highlights).
        question:
          anyOf:
          - type: string
          - type: 'null'
          title: Question
          description: Set when the assistant asks the user to clarify before answering.
        question_options:
          items:
            type: string
          type: array
          title: Question Options
          description: Suggested replies to `question`.
        changed_blueprint_ids:
          items:
            type: string
          type: array
          title: Changed Blueprint Ids
          description: Sheets the assistant changed (tools=edit only).
        degraded:
          type: boolean
          title: Degraded
          description: 'True when the answer is partial: the turn ran out of time or failed after it began.'
          default: false
      type: object
      title: Answer
    Attachment:
      properties:
        name:
          type: string
          maxLength: 256
          minLength: 1
          title: Name
        mime_type:
          type: string
          enum:
          - image/png
          - image/jpeg
          - application/pdf
          title: Mime Type
        data_base64:
          type: string
          maxLength: 14000000
          title: Data Base64
      type: object
      required:
      - name
      - mime_type
      - data_base64
      title: Attachment
    api__external__v1__conversations__ErrorResponse:
      properties:
        detail:
          additionalProperties: true
          type: object
          title: Detail
          description: '`error` is a stable code; `message` is for people.'
      type: object
      required:
      - detail
      title: ErrorResponse
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer