cnvs.app · Schema

BoardImportContent

Initial board content applied atomically at create time (`POST /api/boards` `content` field / MCP `create_board`). The combined input arrays may contain at most 2550 entries (counted exactly as supplied — entries are never merged); template-seeded columns do not count as input. The D1 batch is capped at 320 statements (multi-row INSERTs sized against D1's 100-bound-parameters-per-statement limit). In practice the 5 MB request-body cap bites first on image-heavy imports, well before 2550 entries. **Per-item `author`.** Every item in `texts` / `lines` / `images` / `columns` / `tasks` / `lanes` accepts an optional `author` (1–80 chars of `[A-Za-z0-9:_\-.]`, trimmed — the same rule as the REST/WS paths). Absent or null inherits the body-level `author` (default `ai:import`); an invalid value is `400 {error:"invalid_item", kind, index, reason}`. This exists because an item's `author` is its CREATOR and is never rewritten, so restoring someone else's board must be able to carry the original authorship instead of stamping everything as the importer. **`sourceId` (texts and images).** Optional string, 1–128 chars, unique across the whole import. WRITE-ONLY: it is never stored and never becomes the row id — item ids are always server-minted and returned in the response's `ids`. Its only effect is anchor resolution: a `lines[i].anchors.start.id` / `.end.id` matching a declared `sourceId` is rewritten to that item's minted id. An anchor id matching nothing is left verbatim and renders as a free stroke end. **Strict value checks.** `tasks[].done` must be a real boolean (or null/omitted); a truthy stand-in like `"false"` is `400 {error:"invalid_item", kind:"tasks", index, reason}` rather than being coerced into a COMPLETED card. `lanes` may name each lane index at most once; a second entry for the same index is `400 {error:"invalid_item", kind:"lanes", index, reason}` on that second entry, since collapsing it would overwrite the first entry's `author`. Draw kinds (texts/lines/images) require draw mode; kanban kinds (columns/tasks/lanes/colWidth) require `mode: "todo"` — a mismatch is `400 content_mode_mismatch`. All standard per-board quotas also apply (live values in /quotas.json).

CompanyWhiteboardCollaborationReal-TimeKanbanMCPAI AgentsDiagramming

Properties

Name Type Description
texts array
lines array Freehand strokes. `strokes` is accepted as an alias key (when both are present, `lines` wins).
images array
columns array Todo mode. Array order = sort order. When empty/absent on a todo board, the `template` columns are seeded instead (and `tasks[].columnIndex` indexes into those).
tasks array
lanes array Row (lane) titles for multi-row kanban layouts. At most 10 entries; each `lane` must be used by a column created in this request. Each lane index may appear AT MOST ONCE — a second entry for the same
colWidth number Shared kanban column width in px, clamped to [200, 480].
View JSON Schema on GitHub

JSON Schema

cnvs-app-board-import-content-schema.json Raw ↑
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cnvs-app/json-schema/cnvs-app-board-import-content-schema.json",
  "title": "BoardImportContent",
  "description": "Initial board content applied atomically at create time (`POST /api/boards` `content` field / MCP `create_board`). The combined input arrays may contain at most 2550 entries (counted exactly as supplied — entries are never merged); template-seeded columns do not count as input. The D1 batch is capped at 320 statements (multi-row INSERTs sized against D1's 100-bound-parameters-per-statement limit). In practice the 5 MB request-body cap bites first on image-heavy imports, well before 2550 entries.\n\n**Per-item `author`.** Every item in `texts` / `lines` / `images` / `columns` / `tasks` / `lanes` accepts an optional `author` (1–80 chars of `[A-Za-z0-9:_\\-.]`, trimmed — the same rule as the REST/WS paths). Absent or null inherits the body-level `author` (default `ai:import`); an invalid value is `400 {error:\"invalid_item\", kind, index, reason}`. This exists because an item's `author` is its CREATOR and is never rewritten, so restoring someone else's board must be able to carry the original authorship instead of stamping everything as the importer.\n\n**`sourceId` (texts and images).** Optional string, 1–128 chars, unique across the whole import. WRITE-ONLY: it is never stored and never becomes the row id — item ids are always server-minted and returned in the response's `ids`. Its only effect is anchor resolution: a `lines[i].anchors.start.id` / `.end.id` matching a declared `sourceId` is rewritten to that item's minted id. An anchor id matching nothing is left verbatim and renders as a free stroke end.\n\n**Strict value checks.** `tasks[].done` must be a real boolean (or null/omitted); a truthy stand-in like `\"false\"` is `400 {error:\"invalid_item\", kind:\"tasks\", index, reason}` rather than being coerced into a COMPLETED card. `lanes` may name each lane index at most once; a second entry for the same index is `400 {error:\"invalid_item\", kind:\"lanes\", index, reason}` on that second entry, since collapsing it would overwrite the first entry's `author`.\n\nDraw kinds (texts/lines/images) require draw mode; kanban kinds (columns/tasks/lanes/colWidth) require `mode: \"todo\"` — a mismatch is `400 content_mode_mismatch`. All standard per-board quotas also apply (live values in /quotas.json).",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cnvs-app-openapi.yml#/components/schemas/BoardImportContent",
  "type": "object",
  "properties": {
    "texts": {
      "type": "array",
      "maxItems": 500,
      "items": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "description": "Optional with `autolayout: true`, required otherwise."
          },
          "y": {
            "type": "number",
            "description": "Optional with `autolayout: true`, required otherwise."
          },
          "content": {
            "type": "string",
            "description": "Required, non-empty. cnvs markup + Mermaid supported."
          },
          "color": {
            "type": "string",
            "description": "Named ink only (red/blue/green/…); anything else clamps to auto."
          },
          "width": {
            "type": "number",
            "description": "Explicit width in px (160–4096)."
          },
          "postit": {
            "type": "boolean"
          },
          "diagram": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "link"
            ],
            "description": "`link` renders the content as a URL capsule. Defaults to `text`."
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          },
          "sourceId": {
            "type": "string",
            "maxLength": 128,
            "description": "Optional caller-side handle, unique across the import. Write-only: never stored, never the row id — it only lets `lines[].anchors` endpoints be remapped to this text's server-minted id."
          }
        },
        "required": [
          "content"
        ]
      }
    },
    "lines": {
      "type": "array",
      "maxItems": 2000,
      "description": "Freehand strokes. `strokes` is accepted as an alias key (when both are present, `lines` wins).",
      "items": {
        "type": "object",
        "properties": {
          "points": {
            "description": "Nested `[[x,y],...]`, flat `[x1,y1,...]`, or a JSON string of either. Required, non-empty."
          },
          "color": {
            "type": "string"
          },
          "anchors": {
            "description": "Optional `{start?, end?}` endpoint anchors (same shape as POST /strokes) — each endpoint is `{kind:'text'|'image', id, dx, dy}`; an endpoint failing that shape is dropped, and anchors with neither endpoint left collapse to null. An endpoint `id` matching a `sourceId` declared by a text/image in this same import is rewritten to that item's minted id; an id matching nothing is kept verbatim and renders as a free end."
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          }
        },
        "required": [
          "points"
        ]
      }
    },
    "images": {
      "type": "array",
      "maxItems": 50,
      "items": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "description": "Optional with `autolayout: true`, required otherwise."
          },
          "y": {
            "type": "number",
            "description": "Optional with `autolayout: true`, required otherwise."
          },
          "dataUrl": {
            "type": "string",
            "description": "`data:image/(png|jpeg|gif|webp|svg+xml);base64,...`, ≤ ~900 kB. Required — hosted URLs are not accepted. Only image payloads are accepted: the decoded header bytes are sniffed (PNG/JPEG/GIF/WebP magic bytes or an `<svg>` root), so ordinary PDFs, archives and other files relabeled as images are rejected with 400 (a header check, not a full image decode)."
          },
          "width": {
            "type": "number"
          },
          "height": {
            "type": "number"
          },
          "thumbDataUrl": {
            "type": "string",
            "description": "Optional ≤8 kB raster thumbnail for SVG previews."
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          },
          "sourceId": {
            "type": "string",
            "maxLength": 128,
            "description": "Optional caller-side handle, unique across the import. Write-only: never stored, never the row id — it only lets `lines[].anchors` endpoints be remapped to this image's server-minted id."
          }
        },
        "required": [
          "dataUrl",
          "width",
          "height"
        ]
      }
    },
    "columns": {
      "type": "array",
      "maxItems": 200,
      "description": "Todo mode. Array order = sort order. When empty/absent on a todo board, the `template` columns are seeded instead (and `tasks[].columnIndex` indexes into those).",
      "items": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "lane": {
            "type": "integer",
            "description": "Row index (≥ 0). Defaults to 0."
          },
          "color": {
            "type": "string",
            "enum": [
              "red",
              "blue",
              "green"
            ],
            "description": "Optional title color; anything else → default."
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          }
        },
        "required": [
          "title"
        ]
      }
    },
    "tasks": {
      "type": "array",
      "maxItems": 1000,
      "items": {
        "type": "object",
        "properties": {
          "columnIndex": {
            "type": "integer",
            "description": "Index into `content.columns` (or the template-seeded columns when `content.columns` is empty/absent)."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "description": "Stored inside the task's opaque `content` JSON, same as the UI."
          },
          "due_date": {
            "type": "string",
            "description": "ISO 8601 date."
          },
          "priority": {
            "type": "string",
            "enum": [
              "H",
              "M",
              "L"
            ]
          },
          "assignee": {
            "type": "string"
          },
          "done": {
            "type": "boolean",
            "description": "Completion flag. Must be a REAL boolean — a truthy stand-in such as the string `\"false\"` or `0` is rejected `400 {error:\"invalid_item\", kind:\"tasks\", index, reason:\"done must be true or false (or omitted)\"}` and nothing is created (coercing `\"false\"` would persist the card as COMPLETED). `null` or omitted means not done."
          },
          "color": {
            "type": "string",
            "description": "Card color, stored inside the task's opaque `content` JSON alongside `description`."
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          }
        },
        "required": [
          "columnIndex",
          "name"
        ]
      }
    },
    "lanes": {
      "type": "array",
      "maxItems": 10,
      "description": "Row (lane) titles for multi-row kanban layouts. At most 10 entries; each `lane` must be used by a column created in this request. Each lane index may appear AT MOST ONCE — a second entry for the same `lane` is rejected `400 {error:\"invalid_item\", kind:\"lanes\", index, reason}` (the `index` is that of the SECOND entry) rather than collapsing last-write-wins, because collapsing would let the later entry overwrite the first namer's `author`, and an item's author is its creator and is never rewritten. Every entry still counts separately toward both the 10-lane-title and 2550-total-input-entry limits, and the 10-entry quota error is reported before the duplicate check. Lanes get no id — they are keyed by `(board_id, lane)`, so they have no entry in the response's `ids`.",
      "items": {
        "type": "object",
        "properties": {
          "lane": {
            "type": "integer",
            "description": "Row index (≥ 0)."
          },
          "title": {
            "type": "string"
          },
          "author": {
            "type": "string",
            "description": "Optional per-item creator tag (1–80 chars of `[A-Za-z0-9:_\\-.]`). Omit to inherit the body-level `author`."
          }
        },
        "required": [
          "lane",
          "title"
        ]
      }
    },
    "colWidth": {
      "type": "number",
      "description": "Shared kanban column width in px, clamped to [200, 480]."
    }
  }
}

Work with this as data

Every JSON Schema 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 schemas

4 MCP tools reach this
  • find_json_schemasBrowse and filter every JSON Schema in the catalog.
  • 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 JSON Schema
curl "https://apis.io/api/v1/json-schemas/cnvs-app-board-import-content"
All schemas
curl "https://apis.io/api/v1/json-schemas?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.