---
title: "Add a markup to a sheet"
method: POST
path: "/v1/blueprints/{blueprint_id}/markups"
tags: ["Blueprints / Markups"]
---

# Add a markup to a sheet

`POST /v1/blueprints/{blueprint_id}/markups`

## Request

- Base URL: `https://api.kamai.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.kamai.io/v1/blueprints/{blueprint_id}/markups`
- Auth: HTTP bearer

Add a revision cloud, a highlight or a note to the sheet; positions are fractions
(0..1) of the page from the top-left corner. Answers 201 with the new markup.

A cloud takes `box` (or `points`, an outline); a highlight takes `points`; a note takes
`box`, `text` and optionally `pointer`, the spot its arrow points at. The room under the
pointer is recorded with the note (`room`, `room_id`). Clouds are numbered Δ1, Δ2… per
sheet. Send a `request_id` so a retry is safe: the same `request_id` returns the first
markup with 200 instead of making a second one.

422 for a wrong shape for the kind (a highlight with a box, a note without text, a
pointer on anything but a note), a point outside 0..1, a bad style, a `request_id` used
on another sheet, or a sheet that already holds 300 markups.

## Path parameters

- `blueprint_id` string, required

## Request body

- MarkupCreate
  - `kind` 'cloud' | 'highlight' | 'note', required: cloud: a revision cloud (box or points); highlight: a marker stroke (points); note: a text box (box and text, optionally pointer).
  - `box` number[], nullable: [x0, y0, x1, y1], fractions of the page from the top-left. A cloud wraps it; a note's text sits in it.
  - `points` array[], nullable: [[x, y], ...], fractions of the page. A highlight's stroke; a cloud may follow an outline instead of a box.
    - number[]
  - `pointer` number[], nullable: A note's arrow tip, [x, y] fractions of the page. The room under it is recorded with the note.
  - `text` string, nullable: A note's text, up to 2,000 characters; required on a note.
  - `style` object, nullable: Optional color (#rrggbb), width in PDF points (0.25..20; a highlight up to 60), opacity (0.05..1), font_size in points (4..72, notes only). Left out: the kind's default.
  - `request_id` string, nullable: Repeat the same value on a retry and the first markup is returned instead of a second one.

## Response `200`

A retry with a `request_id` already used: the first markup, unchanged.

- MarkupRecord
  - `id` string, required: The markup's id.
  - `kind` 'cloud' | 'highlight' | 'note', required: `cloud`: a revision cloud; `highlight`: a marker stroke; `note`: a text box, optionally with an arrow.
  - `blueprint_id` string, required: The sheet it is drawn on.
  - `box` number[], nullable: Its bounding box, [x0, y0, x1, y1] in fractions (0..1) of the page from the top-left corner.
  - `points` array[], nullable: A highlight's stroke, or a cloud's outline when it is not a plain box, as [[x, y], ...] page fractions.
    - number[]
  - `pointer` number[], nullable: A note's arrow tip, [x, y] page fractions.
  - `text` string, nullable: The note's text.
  - `style` object: `color` (#RRGGBB), `width` and `font_size` in PDF points, `opacity` 0..1; defaults filled in.
  - `version` integer, required: Increases on every change; send it back on an update or a delete.
  - `can_edit` boolean: Whether you may change or delete it (its author or the project owner).
  - `author` string, nullable: The author's display name, or `null` when the account has none.
  - `source` 'app' | 'chat' | 'mcp', nullable: Where it was made: `app` (the Kamai app), `chat` (its assistant) or `mcp` (this API or an MCP client).
  - `created_at` string, nullable: When it was made (ISO 8601, UTC).
  - `updated_at` string, nullable: When it last changed (ISO 8601, UTC).
  - `delta` integer, nullable: A cloud's revision number, shown as Δ1, Δ2… per sheet; never reused after a delete.
  - `room` string, nullable: The room a note's arrow points at, by name.
  - `room_id` string, nullable: That room's id as `{blueprint_id}::{room id}`; the part after `::` is the room's feature `id`. Re-resolved on a list read when a room still covers the arrow tip; create/patch/delete responses carry the id from creation time.
  - `element` string, nullable: The element a note was pinned to in the app, by name (instead of `room`).
  - `element_id` string, nullable: That element's id, as the app recorded it.
  - `deleted` boolean, nullable: `true` on the record a delete returns.

## Other responses

- `201`: Successful Response
- `404`: The sheet is not in one of your projects, or the markup is not on this sheet.
- `409`: The sheet has no page size yet (still processing), or, for a clip, its PDF is not available.
- `422`: The request is refused. A wrong shape for the kind, a point outside the page, a bad style, a `request_id` used on another sheet, or a full sheet. These answer `{"detail": {"error", "details"}}`. A parameter or body field of the wrong type or out of its range is refused before the request is read, with `{"detail": [{"loc", "msg", "type"}, ...]}`.
- `503`: Markups are not available on this server yet.

---

[API](https://skmtc.dev/kamai/apis/kamai-api.md) · [All operations](https://skmtc.dev/kamai/apis/kamai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/kamai/apis/kamai-api/revisions/78f938546270?raw)
