Kolbo.AIKolbo.AI Docs
Developer API

Kolbo Review

Frame.io-style client review over the API — upload media, create review assets with versions, comment with timecodes, set status, and mint guest share links.

Kolbo Review is the in-app Frame.io-style review layer: versioned assets, status workflow, timestamped comments, collections (folders), and guest share links. The Developer API exposes the same surface under /api/v1/review/* so API clients and @kolbo/mcp agents can drive review without opening the app.

Upload first with the Media Library (POST /v1/media/upload, upload ticket, or MCP upload_media / create_upload_ticket / media_upload_widget), then pass the returned media_id into Review. There is no separate Review uploader.

Endpoint map

GET    /api/v1/review/storage-usage
POST   /api/v1/review/assets
GET    /api/v1/review/assets
GET    /api/v1/review/assets/:id
PATCH  /api/v1/review/assets/:id
POST   /api/v1/review/assets/:id/versions
POST   /api/v1/review/assets/:id/status
DELETE /api/v1/review/assets/:id
GET    /api/v1/review/assets/:id/comments
POST   /api/v1/review/assets/:id/comments

POST   /api/v1/review/collections
GET    /api/v1/review/collections
PATCH  /api/v1/review/collections/:id
DELETE /api/v1/review/collections/:id

POST   /api/v1/review/assets/:id/share-links
POST   /api/v1/review/collections/:id/share-links
GET    /api/v1/review/share-links
POST   /api/v1/review/share-links/:linkId/revoke

POST   /api/v1/review/comments/:noteId/reply
PATCH  /api/v1/review/comments/:noteId
DELETE /api/v1/review/comments/:noteId
POST   /api/v1/review/comments/:noteId/resolve
POST   /api/v1/review/comments/:noteId/unresolve

All Review routes share the 120/min media-library rate-limit bucket (keyed by authenticated user — see Errors & Limits).

Permissions & limits

ActionRequired access
Get one asset, list/create comments, reply, resolve / unresolveProject view+
List assets / collections with an explicit project_idProject edit+ (the SDK project resolver rejects view-only projects with 403 SDK_PROJECT_ACCESS_DENIED)
Create / update assets & collections, add versions, set status, create / list share linksProject edit+
Delete an asset or collectionIts owner (creator), or project full+
Revoke a share linkThe link's creator, or project edit+
Edit a commentComment author only
Delete a commentComment author, or project full+

Additional product limits (same as the app):

LimitDetail
Free planMax 3 live review assets per owner without an active paid monthly/annual subscription (403 REVIEW_FREE_LIMIT)
Storage5 GB of Review version media per owner (413 REVIEW_STORAGE_LIMIT). Counted against the asset owner, even when another editor adds the version.
Per-file uploadMedia upload rules still apply (Review versions reuse Media Library items)

Guest share-link visitors use the public /review-link/:token app page — they are not authenticated via API key. The SDK only manages links as the project editor.

Typical flow

1. POST /v1/media/upload  →  media_id
2. POST /v1/review/assets { name, media_id, project_id }  →  asset (+ v1)
3. POST /v1/review/assets/:id/comments { body, time_start? }
4. POST /v1/review/assets/:id/status { review_status: "needs_review" }
5. POST /v1/review/assets/:id/share-links  →  share_url for the client

Storage usage

GET /api/v1/review/storage-usage
{
  "success": true,
  "used_bytes": 118845242,
  "cap_bytes": 5368709120
}

Scoped to the API-key owner (the same cap that gates create / add-version). used_bytes sums the media size of every version across your non-deleted review assets.

Assets

Create (asset + v1 in one call)

POST /api/v1/review/assets
FieldTypeRequiredDescription
namestringYesDisplay name (trimmed, truncated to 500 chars).
media_idstringYesMediaLibraryItem id from upload / list media. Must be owned by you or live in the same project_id. One media item per call — add more as versions.
project_idstringNoTarget project (edit+). Omitted → auto-created "API Generations" project.
collection_idstringNoOptional Review collection folder. Must belong to the same project (404 otherwise).
version_notestringNoNote on v1 (truncated to 1000 chars).
curl -X POST https://api.kolbo.ai/api/v1/review/assets \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Hero cut v1",
    "media_id": "6a7b42c150af38464a4ff468",
    "project_id": "6a098ebadfe025fd179a074a"
  }'

Response (201):

{
  "success": true,
  "asset": {
    "id": "6a7b463b2e9279d87f9ba2e2",
    "name": "Hero cut v1",
    "project_id": "6a098ebadfe025fd179a074a",
    "collection_id": null,
    "review_status": "in_progress",
    "current_version_index": 0,
    "versions": [
      {
        "id": "…",
        "label": "v1",
        "media_id": "6a7b42c150af38464a4ff468",
        "url": "https://media.kolbo.ai/…",
        "media_type": "video",
        "thumbnail_url": "https://media.kolbo.ai/…",
        "note": null,
        "uploaded_by": "6a01f0c2a51cce019cae1001",
        "uploaded_at": "2026-08-11T15:56:00.000Z"
      }
    ],
    "cover_url": null,
    "owner_id": "6a01f0c2a51cce019cae1001",
    "status_changed_at": null,
    "created_at": "2026-08-11T15:56:00.000Z",
    "updated_at": "2026-08-11T15:56:00.000Z"
  }
}

List

GET /api/v1/review/assets
ParameterTypeRequiredDescription
project_idstringNoProject to list (edit+; alias project). Omitted → your default "API Generations" project (not every accessible project).
collection_idstringNoFilter to one collection (alias collection).
statusstringNoin_progress | needs_review | approved | changes_requested. Also accepted as review_status.
pagenumberNo1-indexed. Default 1.
limitnumberNoDefault 20, min 1, max 100.

Sorted by most recently updated first.

{
  "success": true,
  "assets": [ /* asset shapes */ ],
  "pagination": { "page": 1, "limit": 20, "total": 3, "pages": 1 }
}

Get / update / delete

GET    /api/v1/review/assets/:id
PATCH  /api/v1/review/assets/:id
DELETE /api/v1/review/assets/:id

GET needs view+; PATCH needs edit+. Both return { "success": true, "asset": { … } }. The PATCH, add-version and set-status responses do not populate version media, so each version's url, media_type and thumbnail_url come back null there — GET the asset for those fields.

PATCH body (all optional):

FieldTypeDescription
namestringRename (cannot be empty).
collection_idstring | nullMove into a collection in the same project, or null to uncollect.
current_version_indexnumberSwitch the active version (0-based; must be < versions.length).

DELETE (asset owner or full+) soft-deletes the asset and all comments on its versions, returning { "success": true }. It stops counting toward the free-tier limit and storage usage immediately. Media you uploaded through the Media Library stays in your library; only media created by the in-app Review uploader (and not used by another live review asset) is removed.

Add a version

POST /api/v1/review/assets/:id/versions
FieldTypeRequiredDescription
media_idstringYesNew MediaLibraryItem to append as v{n+1}. Same rule as create: owned by you or in the asset's project.
version_notestringNoOptional note (truncated to 1000 chars).

Becomes the current version automatically. Returns 201 with { "success": true, "asset": { … } }.

Set status

POST /api/v1/review/assets/:id/status
FieldTypeRequiredDescription
review_statusstringYesin_progress | needs_review | approved | changes_requested. Also accepted as status.

Returns { "success": true, "asset": { … } } with the new review_status and status_changed_at. Notifies the asset owner when the changer is someone else (same as the app).

Collections

Folders that group review assets inside a project.

POST   /api/v1/review/collections
GET    /api/v1/review/collections
PATCH  /api/v1/review/collections/:id
DELETE /api/v1/review/collections/:id

Create body: { "name": string, "project_id": string } — both required (400 VALIDATION without project_id; there is no default project here), edit+. Name is truncated to 200 chars. Returns 201.
List query: project_id optional (alias project; defaults to API Generations, same as assets). Not paginated; most recently updated first.
Patch body: { "name": string } (required, truncated to 200 chars), edit+.
Delete: collection owner or full+. Soft-delete; assets in that folder become uncollected (collection_id: null).

Collection shape:

{
  "success": true,
  "collection": {
    "id": "6a7b47e1a51cce019cae9701",
    "name": "Launch cuts",
    "project_id": "6a098ebadfe025fd179a074a",
    "cover_url": null,
    "owner_id": "6a01f0c2a51cce019cae1001",
    "created_at": "2026-08-11T15:57:00.000Z",
    "updated_at": "2026-08-11T15:57:00.000Z"
  }
}

List returns { "success": true, "collections": [ … ] }.

Comments

Text comments (optional video timecodes) on a review asset's media. Drawing annotations and voice notes stay app-only for now.

List / create on an asset

GET  /api/v1/review/assets/:id/comments
POST /api/v1/review/assets/:id/comments

List query: optional version_media_id (defaults to the asset's current version media; must be the media_id of one of the asset's versions, else 400). The list is flat — top-level comments and replies together (replies carry parent_note_id), sorted by time_start then creation time. Not paginated.

Create body:

FieldTypeRequiredDescription
bodystringYes (in practice)Plain-text comment, max 5000 chars. The server does not reject an empty body, but the comment would be blank.
time_startnumberNoSeconds on the timeline. Omit for a general comment.
time_endnumberNoEnd of a range in seconds; must be >= time_start or the comment is rejected.
version_media_idstringNoComment on a non-current version's media (a version's media_id).
curl -X POST https://api.kolbo.ai/api/v1/review/assets/6a7b463b2e9279d87f9ba2e2/comments \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Cut the logo sting shorter", "time_start": 3.2 }'

Comment shape (create returns 201 with comment; list returns comments):

{
  "success": true,
  "comment": {
    "id": "6a7b4810a51cce019cae9720",
    "body": "Cut the logo sting shorter",
    "author": { "id": "6a01f0c2a51cce019cae1001", "first_name": "Dana", "last_name": "Levi", "avatar_url": null },
    "author_kind": "user",
    "time_start": 3.2,
    "time_end": null,
    "resolved": false,
    "reply_count": 0,
    "parent_note_id": null,
    "media_id": "6a7b42c150af38464a4ff468",
    "created_at": "2026-08-11T15:59:00.000Z",
    "updated_at": "2026-08-11T15:59:00.000Z",
    "edited_at": null
  }
}

Reply / edit / delete / resolve

POST   /api/v1/review/comments/:noteId/reply
PATCH  /api/v1/review/comments/:noteId
DELETE /api/v1/review/comments/:noteId
POST   /api/v1/review/comments/:noteId/resolve
POST   /api/v1/review/comments/:noteId/unresolve

Reply body: { "body": string }. One level of threading only — replying to a reply returns 400 NESTED_REPLY_FORBIDDEN. Returns 201 with the reply as comment.
Edit body: optional body, time_start, time_end (author only, else 403 NOT_AUTHOR). Sets edited_at.
Delete: author, or full+ on the project (else 403 NOT_AUTHORIZED). Also deletes the comment's replies. Returns { "success": true }.
Resolve / unresolve: view+; returns the updated comment.

Mint a guest URL the client opens without a Kolbo account.

POST /api/v1/review/assets/:id/share-links
POST /api/v1/review/collections/:id/share-links
GET  /api/v1/review/share-links?target_type=asset|collection&target_id=…
POST /api/v1/review/share-links/:linkId/revoke

Create body (all optional; you need edit+ on the project). Returns 201.

FieldTypeDefaultDescription
role_labelstring"Client"Label shown to guests (truncated to 40 chars).
permissionsobjectsee belowBoolean flags; unknown keys and non-boolean values are ignored, omitted keys take their default.
require_emailbooleantrueAsk guests for an email before commenting.
passwordstringnoneOptional password gate (stored hashed, never returned).
allowed_emailsstring[][]Optional allow-list. Entries without @ are dropped; stored lowercased.
expires_atstringneverISO-8601 expiry; must be in the future (400 VALIDATION).

permissions keys and defaults: canComment (true), canDownload (false), canViewOtherComments (true), canResolveOwn (false), canSwitchVersions (true), canSetStatus (false).

List query: target_type (asset | collection) and target_id are both required (400 VALIDATION otherwise); edit+. Returns { "success": true, "share_links": [ … ] }, newest first. The list includes revoked links: check revoked (boolean). revoked_at is the revocation time (for a revoked link, the time the link was last updated), null while the link is active.

Revoke returns { "success": true }.

{
  "success": true,
  "share_link": {
    "id": "6a7b4797a51cce019cae9685",
    "share_url": "https://app.kolbo.ai/review-link/76642589a6c554d156e16f33b8108ba0…",
    "target_type": "asset",
    "target_id": "6a7b463b2e9279d87f9ba2e2",
    "project_id": "6a098ebadfe025fd179a074a",
    "role_label": "Client",
    "permissions": {
      "canComment": true,
      "canDownload": false,
      "canViewOtherComments": true,
      "canResolveOwn": false,
      "canSwitchVersions": true,
      "canSetStatus": false
    },
    "require_email": true,
    "expires_at": null,
    "revoked": false,
    "revoked_at": null,
    "created_at": "2026-08-11T15:58:00.000Z"
  }
}

Treat share_url as a credential. Only editors can list links; editors or the link's creator can revoke. Revoking immediately invalidates the guest page. share_url carries attribution query parameters — use it exactly as returned.

Errors

StatusCodeCause
400VALIDATIONMissing name / media_id / collection project_id, bad ids, invalid status, out-of-range current_version_index, past expires_at, missing share-link list query, no media version on the asset.
400SDK_PROJECT_INVALID_IDproject_id is not a valid id.
400INVALID_NOTEComment validation failed (e.g. body over 5000 chars).
400NESTED_REPLY_FORBIDDENReplying to a reply.
400INVALID_NOTE_IDMalformed comment id.
403FORBIDDEN / INSUFFICIENT_PERMISSIONNo access to the project / permission tier too low.
403SDK_PROJECT_ACCESS_DENIEDproject_id given but you have less than edit on it.
403NOT_AUTHOR / NOT_AUTHORIZEDEditing someone else's comment / deleting it without full+.
403PROJECT_ACCESS_DENIED / NOTE_ACCESS_DENIEDNo access to the project of the comment's media (or, for media outside any project, you are not its owner).
403REVIEW_FREE_LIMITFree plan already has 3 live review assets.
404NOT_FOUNDAsset, collection, media, or link not found / not accessible.
404SDK_PROJECT_NOT_FOUNDProject not found or not accessible.
404NOTE_NOT_FOUNDComment not found or deleted.
413REVIEW_STORAGE_LIMITOwner would exceed the 5 GB Review storage cap.
429—Over the 120/min bucket.

Envelope: { "success": false, "error": "…", "code": "…" }.

MCP tools

Matching @kolbo/mcp tools (v1.66.0+):

ToolMaps to
get_review_storage_usageGET /v1/review/storage-usage
list_review_assets / get_review_asset / create_review_asset / update_review_asset / add_review_version / set_review_status / delete_review_assetAssets
list_review_collections / create_review_collection / update_review_collection / delete_review_collectionCollections
list_review_comments / create_review_comment / reply_review_comment / edit_review_comment / delete_review_comment / resolve_review_comment / unresolve_review_commentComments
create_review_share_link / list_review_share_links / revoke_review_share_linkShare links

Intended agent flow:

1. upload_media | create_upload_ticket | media_upload_widget  → media_id
2. (user named a project?) list_projects → project_id
3. create_review_asset { name, media_id, project_id }
4. create_review_comment / set_review_status / create_review_share_link as needed

Out of scope (v1)

Drawing annotations / pins, voice-note comments, storyboard share links, live cursors, and guest-token auth for MCP. Use the Kolbo app for those.