{"openapi":"3.1.0","info":{"title":"Soleil Clusters API","version":"1.0.0","description":"Read and write your boards from your own software, or from an AI assistant.\n\nA token acts **as you**: it reaches exactly what your account reaches and nothing more, because the API exchanges it for your own session and every call runs under the same row-level security the app uses.\n\nScopes are `read`, `write` and `delete`. Every token can read; `write` covers creating, editing and moving; `delete` is separate so a token can be allowed to add things without being allowed to destroy them.\n\nRate limit: 1000 requests per hour per token. Every response carries `X-RateLimit-Remaining` and `X-RateLimit-Reset`.","license":{"name":"Proprietary"}},"servers":[{"url":"https://clusters.soleilpictures.com/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A personal access token from Settings → API. Looks like `sk_live_…`."}},"schemas":{"Card":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Board":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Written for a person."},"code":{"type":"string","description":"Stable. Branch on this, not on the message.","enum":["invalid_token","rate_limited","insufficient_scope","forbidden","not_found","conflict","bad_request","limit_reached","unsupported_media_type","payload_too_large","method_not_allowed","idempotency_in_progress","session_unavailable","storage_unavailable","upstream_rejected","upstream_error","internal_error"]}}}},"responses":{"Unauthorized":{"description":"Missing, unknown, revoked or expired token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The token lacks the scope, or the account cannot reach that thing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such thing — including things this account cannot see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Over 1000 requests in the last hour. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/me":{"get":{"summary":"Who this token belongs to","operationId":"getMe","responses":{"200":{"description":"The account, its scopes and its remaining rate limit","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"display_name":{"type":["string","null"]},"tier":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"rate_limit":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/workspaces":{"get":{"summary":"Workspaces this account can see","operationId":"listWorkspaces","responses":{"200":{"description":"Workspaces","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/search":{"get":{"summary":"Find boards and cards by text","description":"Searches board names, and card titles and bodies. This is how to find the board about something without listing every board and reading each one.","operationId":"search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["board","card"]},"description":"Restrict to one or the other. Omit for both."},{"name":"workspace","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Matching boards and cards. Card hits carry a 300-character excerpt, not the whole card.","content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"boards":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":["integer","null"]}}},"cards":{"type":"object","properties":{"items":{"type":"array"},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":["integer","null"]}}}}}}}},"400":{"description":"q was shorter than 2 characters, or a filter was not a uuid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/boards":{"get":{"summary":"List boards","operationId":"listBoards","parameters":[{"name":"workspace","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"parent","in":"query","schema":{"type":"string"},"description":"A board id, or \"root\" for top-level boards only."},{"name":"deleted","in":"query","schema":{"type":"string","enum":["1"]},"description":"Pass 1 to list soft-deleted boards instead of live ones."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of boards","content":{"application/json":{"schema":{"type":"object","properties":{"boards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":["integer","null"]}}}}}}}},"post":{"summary":"Create a board","operationId":"createBoard","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":200},"workspace_id":{"type":"string","format":"uuid","description":"Defaults to the personal workspace."},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]}}}}}},"responses":{"201":{"description":"The board","content":{"application/json":{"schema":{"type":"object","properties":{"board":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}},"delete":{"operationId":"deleteBoards","summary":"Soft-delete many boards","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"board_ids":{"type":"array","items":{"type":"string","format":"uuid"}}},"required":["board_ids"]}}}}}},"/boards/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"One board, with its remaining card capacity","operationId":"getBoard","responses":{"200":{"description":"The board and its capacity","content":{"application/json":{"schema":{"type":"object","properties":{"board":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"capacity":{"type":["object","null"]}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Rename, change view, or reparent","operationId":"updateBoard","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"view":{"type":"string","enum":["canvas","list"]},"parent_board_id":{"type":["string","null"],"format":"uuid","description":"null moves it to the top level. Cycles are refused with 409."}}}}}},"responses":{"200":{"description":"The board as it now stands","content":{"application/json":{"schema":{"type":"object","properties":{"board":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace_id":{"type":"string","format":"uuid"},"parent_board_id":{"type":["string","null"],"format":"uuid"},"view":{"type":"string","enum":["canvas","list"]},"deleted":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"409":{"description":"The reparent was refused — usually because it would make a cycle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Soft-delete a board","description":"Recoverable. Requires the `delete` scope. Children are not touched.","operationId":"deleteBoard","responses":{"200":{"description":"The board as it was, and how to put it back","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/boards/{id}/restore":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Restore a soft-deleted board","operationId":"restoreBoard","responses":{"200":{"description":"The restored board","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/boards/{id}/cards":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Every card on a board","operationId":"listCards","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of cards","content":{"application/json":{"schema":{"type":"object","properties":{"board_id":{"type":"string","format":"uuid"},"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":["integer","null"]}}}}}}}},"post":{"summary":"Add cards","description":"Cards without x/y are placed in free space so they cannot cover what is already there. For an image card, upload the bytes first with POST /uploads and pass the image_key it returns.","operationId":"addCards","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"A retry with the same key replays the original response instead of adding the cards twice. Remembered for 24 hours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cards":{"type":"array","maxItems":100,"items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}}},"responses":{"201":{"description":"The cards as they landed. `live: false` means they are saved but an open canvas will not show them until it reloads.","content":{"application/json":{"schema":{"type":"object","properties":{"board_id":{"type":"string"},"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}},"live":{"type":"boolean"}}}}}},"400":{"description":"An unrecognised kind, or more than 100 cards","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The board is at its card cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateCards","summary":"Change many cards on one board","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cards":{"type":"array","description":"Each entry needs an id; every other field is a partial patch","items":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":true}}},"required":["cards"]}}}}},"delete":{"operationId":"deleteCards","summary":"Remove many cards; each is returned in full","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"card_ids":{"type":"array","items":{"type":"string"}}},"required":["card_ids"]}}}}}},"/boards/{id}/cards/{cardId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"cardId","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Change a card in place","description":"Only the fields you send change. The id cannot be changed.","operationId":"updateCard","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}},"responses":{"200":{"description":"The card as it now stands","content":{"application/json":{"schema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["note","image","link","doc"]},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"},"z":{"type":"number"},"title":{"type":["string","null"]},"body":{"type":["string","null"],"description":"The text of the card, whatever kind it is."},"html":{"type":["string","null"]},"url":{"type":["string","null"]},"image_key":{"type":["string","null"],"description":"A key from POST /uploads. Image cards only."},"alt":{"type":["string","null"]},"color":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Remove a card","description":"Requires the `delete` scope. The removed card comes back in full — there is no undo toast on an HTTP call, so the response body IS the undo. POST it back to restore it.","operationId":"deleteCard","responses":{"200":{"description":"The card that was removed","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/boards/{id}/cards/move":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Move cards to another board","operationId":"moveCards","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to_board_id","card_ids"],"properties":{"to_board_id":{"type":"string","format":"uuid"},"card_ids":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"What moved","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/uploads":{"post":{"summary":"Upload an image","description":"The raw bytes go in the body, with Content-Type set to the image type. Returns a key to pass as `image_key` when creating a card. Charged against the board owner's storage, so ?board= is required. 25MB maximum.","operationId":"uploadImage","parameters":[{"name":"board","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/gif":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/heic":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"The stored key, and its dimensions where they could be read","content":{"application/json":{"schema":{"type":"object","properties":{"image_key":{"type":"string"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"bytes":{"type":"integer"},"content_type":{"type":"string"}}}}}},"402":{"description":"That would go past the storage included with this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 25MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Not an image type this API accepts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/multipart":{"post":{"summary":"Begin a multipart upload","description":"For files past the single-request ceiling, and for any file type. The bytes never pass through this API: you receive signed URLs from /uploads/multipart/parts and PUT to them directly. `bytes` is the TOTAL size and is required, because the storage quota is checked up front rather than after the transfer. Needs a paid account on the workspace that owns the board.","operationId":"beginMultipartUpload","parameters":[{"name":"board","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bytes"],"properties":{"bytes":{"type":"integer","description":"Total size of the whole file."},"content_type":{"type":"string"},"filename":{"type":"string","description":"Only used to recover a file extension."}}}}}},"responses":{"201":{"description":"The upload session","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"upload_id":{"type":"string"},"part_size":{"type":"integer"},"part_count":{"type":"integer"},"content_type":{"type":"string"}}}}}},"402":{"description":"That would go past the storage included with this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a writer on that board, or the owner is not on a paid plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/multipart/parts":{"post":{"summary":"Get signed URLs for a batch of parts","description":"PUT each part to the URL returned here, not to this API. Parts may go up in any order and in parallel; keep the ETag from each response for the complete call. Ask in batches as you go — the URLs are time-limited.","operationId":"signUploadParts","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["board_id","key","upload_id","part_numbers"],"properties":{"board_id":{"type":"string","format":"uuid"},"key":{"type":"string"},"upload_id":{"type":"string"},"part_numbers":{"type":"array","maxItems":1000,"items":{"type":"integer","minimum":1,"maximum":10000}}}}}}},"responses":{"200":{"description":"Signed PUT URLs keyed by part number","content":{"application/json":{"schema":{"type":"object","properties":{"urls":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"403":{"description":"Not a writer on that board","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/multipart/complete":{"post":{"summary":"Finish a multipart upload","description":"Assembles the parts into one object and records the row that authorizes reads and keeps it from being reclaimed. Returns the key to pass as `image_key`.","operationId":"completeMultipartUpload","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["board_id","key","upload_id","parts"],"properties":{"board_id":{"type":"string","format":"uuid"},"key":{"type":"string"},"upload_id":{"type":"string"},"parts":{"type":"array","items":{"type":"object","required":["part_number","etag"],"properties":{"part_number":{"type":"integer"},"etag":{"type":"string"}}}}}}}}},"responses":{"201":{"description":"The stored key","content":{"application/json":{"schema":{"type":"object","properties":{"image_key":{"type":"string"},"bytes":{"type":["integer","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}}}}},"403":{"description":"Not a writer on that board","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/multipart/abort":{"post":{"summary":"Discard a multipart upload","description":"Throws away the parts of an upload you have given up on, so they are not billed.","operationId":"abortMultipartUpload","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["board_id","key","upload_id"],"properties":{"board_id":{"type":"string","format":"uuid"},"key":{"type":"string"},"upload_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Discarded","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/images":{"get":{"summary":"List stored images","description":"What is already stored, so an interrupted bulk upload can be resumed by diffing against a local manifest rather than re-sending everything. Paged by CURSOR, not offset: offset paging makes the database walk and discard every row it skips, so it degrades exactly when a listing gets long enough to need paging.","operationId":"listImages","parameters":[{"name":"workspace","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"board","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only images created at or after this time."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `next_cursor` from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"A page of stored images","content":{"application/json":{"schema":{"type":"object","properties":{"images":{"type":"array","items":{"type":"object","properties":{"image_key":{"type":"string"},"bytes":{"type":["integer","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"board_id":{"type":["string","null"],"format":"uuid"},"workspace_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"}}}},"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}}}}}}},"/boards/tree":{"get":{"operationId":"getBoardTree","summary":"A whole board hierarchy in one call","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"root","in":"query","required":false,"description":"A board id to start from","schema":{"type":"string","format":"uuid"}},{"name":"workspace","in":"query","required":false,"description":"Or a whole workspace","schema":{"type":"string","format":"uuid"}},{"name":"depth","in":"query","required":false,"description":"Levels down, default 10, max 20","schema":{"type":"integer","minimum":1,"maximum":20}}]}},"/boards/move":{"post":{"operationId":"moveBoards","summary":"Reparent several boards, cycle-safe","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"board_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"parent_board_id":{"type":["string","null"],"format":"uuid"}},"required":["board_ids"]}}}}}},"/boards/{id}/arrange":{"post":{"operationId":"arrangeBoard","summary":"Lay out cards that already exist","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"layout":{"type":"string","enum":["justified","masonry","grid","row","column"],"description":"justified (default) fits rows flush on both edges"},"card_ids":{"type":"array","items":{"type":"string"},"description":"Omit to arrange the whole board"},"gap":{"type":"number"},"width":{"type":"number","description":"Block width to solve against"},"row_height":{"type":"number","description":"Target row height, justified only"},"columns":{"type":"number","description":"Column cap, grid and masonry only"},"dry_run":{"type":"boolean","description":"Compute the layout and write nothing"}}}}}}}},"/boards/{id}/groups":{"get":{"operationId":"listGroups","summary":"The groups on a board","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"post":{"operationId":"createGroup","summary":"Create a group","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":"string"},"shape":{"type":"string","enum":["box","hug"]},"outline":{"type":"boolean"}},"required":["name"]}}}}}},"/boards/{id}/groups/{groupId}":{"patch":{"operationId":"updateGroup","summary":"Rename or restyle a group","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"groupId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":"string"},"shape":{"type":"string","enum":["box","hug"]},"outline":{"type":"boolean"}}}}}}},"delete":{"operationId":"deleteGroup","summary":"Ungroup — removes the group, never its cards","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"groupId","in":"path","required":true,"schema":{"type":"string"}}]}},"/boards/{id}/import":{"post":{"operationId":"importIntoBoard","summary":"Import reference from remote URLs — re-runnable","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","maxItems":100,"items":{"type":"object","properties":{"url":{"type":"string","description":"An https URL on a public host"},"title":{"type":"string"},"caption":{"type":"string"},"props":{"type":"object","additionalProperties":true},"identifiers":{"type":"array","items":{"type":"object","additionalProperties":true}},"x":{"type":"number"},"y":{"type":"number"},"w":{"type":"number"},"h":{"type":"number"}},"required":["url"]}},"dry_run":{"type":"boolean","description":"Validate without fetching or creating"},"layout":{"type":"string","enum":["justified","masonry","grid","row","column"],"description":"How to arrange what lands. Defaults to justified."}},"required":["items"]}}}}}},"/boards/{id}/export":{"get":{"operationId":"exportBoard","summary":"Export a board as JSON or MovieLabs OMC-JSON","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":false,"description":"json (default) or omc","schema":{"type":"string","enum":["json","omc"]}}]}},"/resolve":{"get":{"operationId":"resolveIdentifier","summary":"Find an object by a foreign identifier","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"scope","in":"query","required":true,"schema":{"type":"string"}},{"name":"value","in":"query","required":true,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"board, card or image","schema":{"type":"string","enum":["board","card","image"]}},{"name":"workspace","in":"query","required":false,"description":"Restrict to one workspace","schema":{"type":"string","format":"uuid"}}]}},"/audit":{"get":{"operationId":"readAudit","summary":"Writes made through the API, and image reads","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"since","in":"query","required":false,"description":"ISO timestamp","schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"From a previous next_cursor","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}]}},"/mcp":{"post":{"operationId":"mcp","summary":"Model Context Protocol endpoint, for AI agents","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"description":"A JSON-RPC 2.0 endpoint speaking MCP. See /docs/mcp — this is not a REST resource and is described here only so it appears in the surface.","requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"A JSON-RPC message or batch"}}}}}},"/webhooks":{"get":{"operationId":"listWebhooks","summary":"Webhooks in a workspace","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"workspace","in":"query","required":false,"description":"Restrict to one workspace","schema":{"type":"string","format":"uuid"}}]},"post":{"operationId":"createWebhook","summary":"Register a webhook; the secret is returned once","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri","description":"Public https, default port"},"events":{"type":"array","items":{"type":"string"},"description":"Event names, or [\"*\"]"},"name":{"type":"string"}},"required":["workspace_id","url","events"]}}}}}},"/webhooks/{id}":{"patch":{"operationId":"updateWebhook","summary":"Change a webhook, or switch it back on","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"active":{"type":"boolean"}}}}}}},"delete":{"operationId":"deleteWebhook","summary":"Remove a webhook","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/webhooks/{id}/test":{"post":{"operationId":"testWebhook","summary":"Send a real delivery, to check signing end to end","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"Every delivery attempt, with its result","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","required":false,"description":"From a previous next_cursor","schema":{"type":"string"}}]}},"/webhooks/{id}/deliveries/{deliveryId}/redeliver":{"post":{"operationId":"redeliverWebhook","summary":"Requeue a delivery","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/service-accounts":{"get":{"operationId":"listServiceAccounts","summary":"Service accounts in a workspace","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"workspace","in":"query","required":true,"schema":{"type":"string","format":"uuid"}}]},"post":{"operationId":"createServiceAccount","summary":"Create one, with its first token","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","write","delete"]}},"ttl_days":{"type":"integer"},"rate_limit":{"type":"integer"}},"required":["workspace_id","name"]}}}}}},"/service-accounts/{id}":{"delete":{"operationId":"deleteServiceAccount","summary":"Retire one and revoke its tokens","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/service-accounts/{id}/tokens":{"get":{"operationId":"listServiceAccountTokens","summary":"Its tokens and when each was last used","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"post":{"operationId":"createServiceAccountToken","summary":"Mint another token — rotate without downtime","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","write","delete"]}},"ttl_days":{"type":"integer"},"rate_limit":{"type":"integer"}}}}}}}},"/service-accounts/{id}/tokens/{tokenId}":{"delete":{"operationId":"revokeServiceAccountToken","summary":"Revoke one token","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"tokenId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/images/{key}":{"get":{"summary":"Fetch an uploaded image","description":"The bytes back, for a key this account can see. The key contains slashes; send it as-is after /images/.\n\nPass `?variant=preview` for the smaller rendition the app stores on upload — around 48kB at roughly 900px, against ~470kB for a typical original. Not every image has one, so this falls back to the original rather than failing; the `X-Image-Variant` response header says which you actually got.","operationId":"getImage","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}},{"name":"variant","in":"query","required":false,"schema":{"type":"string","enum":["original","preview"],"default":"original"},"description":"preview returns a smaller rendition when one exists."}],"responses":{"200":{"description":"The image bytes","headers":{"X-Image-Variant":{"description":"Which rendition was served: `original` or `preview`.","schema":{"type":"string","enum":["original","preview"]}}},"content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}}}}