Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Kamai Projects 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: Projects
description: Projects group sheets. Every key sees only its own user's projects.
paths:
/v1/projects:
get:
tags:
- Projects
summary: List your projects
description: All projects of the API key's user. `last_modified` is in milliseconds since the Unix epoch (UTC).
operationId: list_projects_v1_projects_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectListResponse'
examples:
real:
summary: Two of the user's projects
description: Real response shape from a Kamai test account, 2026-10-13. Names, text and ids are illustrative; numbers and structure are as returned. Trimmed to 2 projects.
value:
projects:
- id: aaaa00000002
name: Ben Yehuda 7 — offices
description: ''
last_modified: 1791457324000
- id: aaaa00000001
name: Herzl 12 — renovation
description: Apartment, 3 rooms
last_modified: 1791204314000
message: Projects fetched successfully
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- HTTPBearer: []
post:
tags:
- Projects
summary: Create a project
description: Create an empty project. Pass its `id` as `project_id` when you upload a sheet into it.
operationId: create_project_v1_projects_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectCreateRequest'
examples:
real:
summary: Name and description
value:
name: Rothschild 45 — tender
description: Residential, 6 floors
required: true
responses:
'201':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectCreateResponse'
examples:
real:
summary: Created
description: Real response shape from a Kamai test account, 2026-10-13. Ids are illustrative.
value:
project:
id: aaaa00000003
name: Rothschild 45 — tender
description: Residential, 6 floors
last_modified: 1791894411000
message: Project created successfully
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- HTTPBearer: []
/v1/projects/{project_id}:
get:
tags:
- Projects
summary: Get a project
description: 'One project with its upload jobs, keyed by job id.
409 `project_unreadable` (with a `request_id`) when the project''s stored record cannot be read; contact support.'
operationId: get_project_v1_projects__project_id__get
security:
- HTTPBearer: []
parameters:
- name: project_id
in: path
required: true
schema:
type: string
title: Project Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectDetailResponse'
examples:
real:
summary: A project with one sheet
description: 'Real response shape from a Kamai test account, 2026-10-13. Names, text and ids are illustrative; numbers and structure are as returned. Trimmed: 1 of 8 sheet records.'
value:
project:
id: aaaa00000001
name: Herzl 12 — renovation
description: Apartment, 3 rooms
last_modified: 1791204314000
jobs: {}
blueprint_records:
bbbbbbbb-0000-4000-8000-000000000001:
id: bbbbbbbb-0000-4000-8000-000000000001
name: A-101 Ground floor
description: '[A-101] GROUND FLOOR PLAN'
created: '2026-03-01T12:18:55.800347Z'
updated: '2026-10-10T18:47:27.386246Z'
ready:
pdf_file: true
main_tile: true
all_tiles: true
preview_image: true
vector_image: true
geojson: true
regions: true
ocr_text: true
ml_metadata: true
ocr_connected: false
autoclassify_polygons: false
message: Project fetched successfully
'409':
description: The project's stored record cannot be read (it does not match the project schema). Nothing was changed; contact support with the request id.
content:
application/json:
examples:
unreadable:
summary: A project whose stored record cannot be read
description: Response shape; ids are illustrative.
value:
detail:
error: project_unreadable
message: This project's stored record cannot be read. Contact support with this request id.
request_id: cccccccc-0000-4000-8000-000000000001
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
patch:
tags:
- Projects
summary: Rename a project or change its description
description: 'Rename a project, change its description, or set how door handing is shown in it
(`handing_convention`). Send at least one of them; `handing_convention: null` clears the setting.
409 `project_unreadable` (with a `request_id`) when the project''s stored record cannot be read; contact support.'
operationId: update_project_v1_projects__project_id__patch
security:
- HTTPBearer: []
parameters:
- name: project_id
in: path
required: true
schema:
type: string
title: Project Id
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdateRequest'
examples:
real:
summary: Rename
value:
name: Rothschild 45 — tender (rev B)
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdateResponse'
examples:
real:
summary: Renamed
description: Real response shape from a Kamai test account, 2026-10-13. Ids are illustrative.
value:
project:
id: aaaa00000003
name: Rothschild 45 — tender (rev B)
description: Residential, 6 floors
last_modified: 1791894411000
message: Project updated successfully
'409':
description: The project's stored record cannot be read (it does not match the project schema). Nothing was changed; contact support with the request id.
content:
application/json:
examples:
unreadable:
summary: A project whose stored record cannot be read
description: Response shape; ids are illustrative.
value:
detail:
error: project_unreadable
message: This project's stored record cannot be read. Contact support with this request id.
request_id: cccccccc-0000-4000-8000-000000000001
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
delete:
tags:
- Projects
summary: Delete a project
description: 'Delete the project and what is in it: its sheets, jobs and integration links. There is no undo through the API.'
operationId: delete_project_v1_projects__project_id__delete
security:
- HTTPBearer: []
parameters:
- name: project_id
in: path
required: true
schema:
type: string
title: Project Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectDeleteResponse'
examples:
real:
summary: Deleted
description: Real response shape from a Kamai test account, 2026-10-13. Ids are illustrative.
value:
project_id: aaaa00000003
message: Project deleted successfully
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/v1/projects/{project_id}/sheets:
get:
tags:
- Projects
summary: List a project's sheets in the app's order
description: 'Every sheet of the project, in the order and with the names the Kamai app shows, so
an integration can show a sheet list that matches the app and let a user pick a sheet.
The order is the natural order of the sheet names (A-2 before A-10), the same for every
user, computed on each call: it can change when an upload finishes or a sheet is
renamed, so keep each sheet''s `blueprint_id` rather than its place. `label` is the name
to show; `display_name`, `sheet_number` and `title` give its parts. Uploads still
processing are not listed until their sheet exists.
403 when the project belongs to another account; 404 when no project has this id.'
operationId: list_sheets_v1_projects__project_id__sheets_get
security:
- HTTPBearer: []
parameters:
- name: project_id
in: path
required: true
schema:
type: string
title: Project Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectSheetsResponse'
example:
project_id: aaaa00000005
sheets:
- blueprint_id: bbbbbbbb-0000-5000-8000-000000000006
label: A-101 FLOOR PLAN
display_name: A-101 FLOOR PLAN
sheet_number: A-101
title: FLOOR PLAN
- blueprint_id: bbbbbbbb-0000-5000-8000-000000000007
label: A-201 ELEVATIONS
display_name: A-201 ELEVATIONS
sheet_number: A-201
title: ELEVATIONS
'403':
description: The project belongs to another account.
'404':
description: No project has this id.
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
SheetPublic:
properties:
blueprint_id:
type: string
title: Blueprint Id
description: 'The sheet''s id. Refer to a sheet by it: the list order can change when an upload finishes or a sheet is renamed.'
label:
type: string
title: Label
description: 'What the Kamai app calls this sheet. Usually equal to display_name; a sheet named only by a meaningless file name reads ''Sheet N · <file name>'', and two sheets that would read the same each get a hint: ''(p. 3)'' is the page of the upload file, ''(#4)'' the place in this list.'
display_name:
type: string
title: Display Name
description: The user's own name if they renamed the sheet, else its title block ('A-101 FLOOR PLAN'), else its file name.
sheet_number:
anyOf:
- type: string
- type: 'null'
title: Sheet Number
description: The sheet number from the title block, e.g. A-101. Not unique and not an address.
title:
anyOf:
- type: string
- type: 'null'
title: Title
description: The sheet title from the title block.
type: object
required:
- blueprint_id
- label
- display_name
title: SheetPublic
ProjectCreateRequest:
properties:
name:
type: string
maxLength: 256
minLength: 1
title: Name
description:
type: string
maxLength: 2048
title: Description
default: ''
type: object
required:
- name
title: ProjectCreateRequest
BlueprintReadyRecord:
properties:
pdf_file:
type: boolean
title: Pdf File
default: false
main_tile:
type: boolean
title: Main Tile
default: false
all_tiles:
type: boolean
title: All Tiles
default: false
preview_image:
type: boolean
title: Preview Image
default: false
vector_image:
type: boolean
title: Vector Image
default: false
geojson:
type: boolean
title: Geojson
default: false
regions:
type: boolean
title: Regions
default: false
ocr_text:
anyOf:
- type: boolean
- type: 'null'
title: Ocr Text
ml_metadata:
type: boolean
title: Ml Metadata
default: false
ocr_connected:
type: boolean
title: Ocr Connected
default: false
autoclassify_polygons:
type: boolean
title: Autoclassify Polygons
default: false
type: object
title: BlueprintReadyRecord
ProjectUpdateResponse:
properties:
project:
$ref: '#/components/schemas/ProjectPublic'
message:
type: string
title: Message
default: Project updated successfully
type: object
required:
- project
title: ProjectUpdateResponse
ProjectSheetsResponse:
properties:
project_id:
type: string
title: Project Id
description: The project.
sheets:
items:
$ref: '#/components/schemas/SheetPublic'
type: array
title: Sheets
description: 'Every sheet of the project in the app''s order: the natural order of the names, the same for every user. Uploads still processing are not listed.'
type: object
required:
- project_id
title: ProjectSheetsResponse
ProjectDetailResponse:
properties:
project:
$ref: '#/components/schemas/ProjectDetailPublic'
message:
type: string
title: Message
default: Project fetched successfully
type: object
required:
- project
title: ProjectDetailResponse
ProjectListResponse:
properties:
projects:
items:
$ref: '#/components/schemas/ProjectPublic'
type: array
title: Projects
message:
type: string
title: Message
default: Projects fetched successfully
type: object
required:
- projects
title: ProjectListResponse
ProjectDeleteResponse:
properties:
project_id:
type: string
title: Project Id
message:
type: string
title: Message
default: Project deleted successfully
type: object
required:
- project_id
title: ProjectDeleteResponse
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
ProjectCreateResponse:
properties:
project:
$ref: '#/components/schemas/ProjectPublic'
message:
type: string
title: Message
default: Project created successfully
type: object
required:
- project
title: ProjectCreateResponse
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
type: object
required:
- loc
- msg
- type
title: ValidationError
ProjectDetailPublic:
properties:
id:
type: string
title: Id
name:
type: string
title: Name
description:
type: string
title: Description
last_modified:
anyOf:
- type: integer
- type: 'null'
title: Last Modified
description: Last modified timestamp in milliseconds
handing_convention:
anyOf:
- type: string
enum:
- us
- din
- type: 'null'
title: Handing Convention
description: 'How door handing is shown in this project: ''us'' (LH / RH, seen from the side the door swings away from) or ''din'' (DIN 107: DIN links / DIN rechts, seen from the side it opens towards). `null` = not set: German readers see DIN 107, everyone else ''us''. Display only: every door still carries both `handing` and `handing_din`.'
jobs:
additionalProperties:
$ref: '#/components/schemas/JobPublic'
type: object
title: Jobs
blueprint_records:
additionalProperties:
$ref: '#/components/schemas/BlueprintRecordPublic'
type: object
title: Blueprint Records
type: object
required:
- id
- name
- description
title: ProjectDetailPublic
ProjectUpdateRequest:
properties:
name:
anyOf:
- type: string
maxLength: 256
minLength: 1
- type: 'null'
title: Name
description:
anyOf:
- type: string
maxLength: 2048
- type: 'null'
title: Description
handing_convention:
anyOf:
- type: string
enum:
- us
- din
- type: 'null'
title: Handing Convention
description: 'How door handing is shown in this project: ''us'' (LH / RH, seen from the side the door swings away from) or ''din'' (DIN 107: DIN links / DIN rechts, seen from the side it opens towards). `null` = not set: German readers see DIN 107, everyone else ''us''. Display only: every door still carries both `handing` and `handing_din`. Send `null` to clear it.'
type: object
title: ProjectUpdateRequest
JobPublic:
properties:
id:
type: string
title: Id
status:
type: string
title: Status
state:
anyOf:
- type: string
- type: 'null'
title: State
blueprint_id:
type: string
title: Blueprint Id
filename:
type: string
title: Filename
progress:
type: integer
title: Progress
default: 0
error_code:
anyOf:
- type: string
- type: 'null'
title: Error Code
error_message:
anyOf:
- type: string
- type: 'null'
title: Error Message
page_index:
anyOf:
- type: integer
- type: 'null'
title: Page Index
description: The page of the uploaded file this job reads, 0-based. `null` until the page is cut from the upload.
auto_rotation:
anyOf:
- type: integer
- type: 'null'
title: Auto Rotation
description: Degrees clockwise (0, 90, 180, 270) Kamai turned this page at upload to bring it upright; 0 = left as the PDF had it. `null` until the page is cut, or when the check did not run. The sheet carries the same value (`auto_rotation` on GET /v1/blueprints/{id}).
type: object
required:
- id
- status
- blueprint_id
- filename
title: JobPublic
ProjectPublic:
properties:
id:
type: string
title: Id
name:
type: string
title: Name
description:
type: string
title: Description
last_modified:
anyOf:
- type: integer
- type: 'null'
title: Last Modified
description: Last modified timestamp in milliseconds
handing_convention:
anyOf:
- type: string
enum:
- us
- din
- type: 'null'
title: Handing Convention
description: 'How door handing is shown in this project: ''us'' (LH / RH, seen from the side the door swings away from) or ''din'' (DIN 107: DIN links / DIN rechts, seen from the side it opens towards). `null` = not set: German readers see DIN 107, everyone else ''us''. Display only: every door still carries both `handing` and `handing_din`.'
type: object
required:
- id
- name
- description
title: ProjectPublic
BlueprintRecordPublic:
properties:
id:
type: string
title: Id
name:
type: string
title: Name
description:
anyOf:
- type: string
- type: 'null'
title: Description
created:
type: string
format: date-time
title: Created
updated:
type: string
format: date-time
title: Updated
ready:
$ref: '#/components/schemas/BlueprintReadyRecord'
default:
pdf_file: false
main_tile: false
all_tiles: false
preview_image: false
vector_image: false
geojson: false
regions: false
ml_metadata: false
ocr_connected: false
autoclassify_polygons: false
type: object
required:
- id
- name
- created
- updated
title: BlueprintRecordPublic
securitySchemes:
HTTPBearer:
type: http
scheme: bearer