{"openapi":"3.1.0","info":{"title":"Proximaforma API","version":"0.2.0","description":"Private product design canvases with accounts, scoped agent connections, reviewer invitations, and shared comment threads. Canvas writes return the updated project; comments have separate revisions."},"servers":[{"url":"/api"}],"security":[{"bearerAuth":[]},{"browserSession":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"},"browserSession":{"type":"apiKey","in":"cookie","name":"pf_session"}},"schemas":{"ElementPatch":{"type":"object","properties":{"id":{"type":"string"},"type":{"enum":["frame","rectangle","ellipse","text","button","image","path"]},"name":{"type":"string"},"parentId":{"type":["string","null"]},"x":{"type":"number"},"y":{"type":"number"},"width":{"type":"number","minimum":1},"height":{"type":"number","minimum":1},"fill":{"type":"string"},"color":{"type":"string"},"stroke":{"type":"string"},"strokeWidth":{"type":"number"},"radius":{"type":"number"},"opacity":{"type":"number","minimum":0,"maximum":1},"rotation":{"type":"number"},"text":{"type":"string"},"fontSize":{"type":"number"},"fontWeight":{"type":"integer"},"textAlign":{"enum":["left","center","right"]},"src":{"type":"string","description":"HTTPS image URL"},"hidden":{"type":"boolean"},"locked":{"type":"boolean"},"vector":{"type":"object","required":["width","height","nodes"],"properties":{"width":{"type":"number","minimum":1,"maximum":10000},"height":{"type":"number","minimum":1,"maximum":10000},"closed":{"type":"boolean","default":false},"nodes":{"type":"array","minItems":2,"maxItems":256,"items":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000},"in":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}},"out":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}}}}}},"description":"Editable path geometry in local coordinates. Closed paths require at least three anchors. Optional in/out handles are absolute cubic Bézier control points."},"strokeCap":{"enum":["butt","round","square"],"default":"round"},"strokeJoin":{"enum":["miter","round","bevel"],"default":"round"}}},"Element":{"type":"object","required":["type"],"properties":{"id":{"type":"string"},"type":{"enum":["frame","rectangle","ellipse","text","button","image","path"]},"name":{"type":"string"},"parentId":{"type":["string","null"]},"x":{"type":"number"},"y":{"type":"number"},"width":{"type":"number","minimum":1},"height":{"type":"number","minimum":1},"fill":{"type":"string"},"color":{"type":"string"},"stroke":{"type":"string"},"strokeWidth":{"type":"number"},"radius":{"type":"number"},"opacity":{"type":"number","minimum":0,"maximum":1},"rotation":{"type":"number"},"text":{"type":"string"},"fontSize":{"type":"number"},"fontWeight":{"type":"integer"},"textAlign":{"enum":["left","center","right"]},"src":{"type":"string","description":"HTTPS image URL"},"hidden":{"type":"boolean"},"locked":{"type":"boolean"},"vector":{"type":"object","required":["width","height","nodes"],"properties":{"width":{"type":"number","minimum":1,"maximum":10000},"height":{"type":"number","minimum":1,"maximum":10000},"closed":{"type":"boolean","default":false},"nodes":{"type":"array","minItems":2,"maxItems":256,"items":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000},"in":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}},"out":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}}}}}},"description":"Editable path geometry in local coordinates. Closed paths require at least three anchors. Optional in/out handles are absolute cubic Bézier control points."},"strokeCap":{"enum":["butt","round","square"],"default":"round"},"strokeJoin":{"enum":["miter","round","bevel"],"default":"round"}}}}},"paths":{"/billing":{"get":{"summary":"Read the current account's free token allowance, EUR credit, and model pricing","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/billing/offer":{"get":{"summary":"Read the configured Stripe EUR refill price","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/billing/checkout":{"post":{"summary":"Create an idempotent prepaid credit Checkout Session","description":"Registered browser account only. Amount and currency come from the server's Stripe Price. The returned Stripe URL collects payment; it does not grant credit.","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestKey":{"type":"string","format":"uuid"},"projectId":{"type":"string","maxLength":100}},"required":["requestKey"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/billing/reconcile":{"post":{"summary":"Reconcile the current account's completed checkout with Stripe","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sessionId":{"type":"string","maxLength":255}},"required":["sessionId"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/billing/history":{"get":{"summary":"Read the current account's latest 100 payment and credit ledger entries","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/billing/stripe/webhook":{"post":{"summary":"Receive signed Stripe payment and refund events","description":"Requires the raw JSON body and Stripe signature. Unrelated application payments are ignored. Successfully paid Proximaforma orders are credited once.","security":[],"parameters":[{"in":"header","name":"stripe-signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/history":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List persistent file history, newest first","description":"Read-only. Returns entries (newest first), nextBefore, nextAfter, and through. Use one cursor: before for older entries, after for newer entries (0 starts at the beginning), or around to resume near an event. Reuse through to freeze the replay endpoint while others keep editing. Each event includes sequence, actor, kind, message, timestamp, canvas revision, status, targets, and an optional commentId.","parameters":[{"name":"before","in":"query","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":200}},{"name":"after","in":"query","schema":{"type":"integer","minimum":0}},{"name":"around","in":"query","schema":{"type":"integer","minimum":1}},{"name":"through","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/history/{sequence}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"sequence","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"get":{"summary":"Read the file and comment threads at a history event","description":"Returns entry, design, and comments as they existed immediately after this event. Optional baseRevision returns design as a change set: baseRevision, changed elements, elementOrder, and target design metadata. Apply against that exact base to reconstruct the preview, including when stepping backward. Without baseRevision, design is complete. Reads never change the live canvas. Requires current access to the file.","parameters":[{"name":"baseRevision","in":"query","schema":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/references":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List linked workspace files with current names, versions, and availability","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Owner: link an accessible workspace file as a design reference","description":"Stores only a sourceProjectId link and optional notes. No uploads or snapshots. Source access is checked before linking and on every reference read. Up to 10 links per canvas; requires its current revision.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sourceProjectId":{"type":"string","minLength":1,"maxLength":100},"notes":{"type":"string","maxLength":1000},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["sourceProjectId","revision"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Updated design including reference links"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/references/{referenceId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"referenceId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read the latest elements and styles in a linked workspace file","description":"Checks target-canvas access and the reader's account access to the source. Canvas-scoped agents can read directly linked files through this endpoint. Source comments, activity, and nested references are excluded. Deleted or inaccessible sources return 404.","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"delete":{"summary":"Owner: remove a reference link","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/session":{"get":{"summary":"Read the current browser account and local-workspace availability","security":[],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Open the existing local workspace (disabled on public-origin deployments)","security":[],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/auth/register":{"post":{"summary":"Register a private account; returns a recovery code once and sets an HttpOnly session cookie","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":15,"maxLength":128}},"required":["name","email","password"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Account and recovery code; session cookie set"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/auth/login":{"post":{"summary":"Sign in with email and password","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","maxLength":128}},"required":["email","password"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/auth/recover":{"post":{"summary":"Recover an account with a single-use recovery code; rotates the code and revokes all previous sessions and agent keys","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"recoveryCode":{"type":"string"},"password":{"type":"string","minLength":15,"maxLength":128}},"required":["email","recoveryCode","password"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/auth/logout":{"post":{"summary":"Revoke the current browser session","security":[{"browserSession":[]}],"responses":{"204":{"description":"Signed out"}}}},"/config":{"get":{"summary":"Connection endpoints; local paths are only returned to local-workspace identities","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/agent-setups":{"get":{"summary":"List setup status for an owned canvas; excludes secrets","security":[{"browserSession":[]}],"parameters":[{"name":"projectId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Create a private 24-hour, single-use agent setup URL and instructions","description":"Replaces the owner's previous unclaimed link for this canvas. Access is fixed by the owner; the agent will choose its own name. The setup token is only returned in the URL fragment and instructions.","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string","minLength":1},"scope":{"enum":["canvas","workspace"],"default":"canvas"},"permission":{"enum":["read","comment","edit"],"default":"edit"},"expiresInDays":{"type":"integer","minimum":1,"maximum":90,"default":30,"description":"Agent connection lifetime, starting at registration"}},"required":["projectId"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"id, url, instructions, expiresAt (Unix milliseconds); no API key is created yet"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/agent-setups/{setupId}":{"parameters":[{"name":"setupId","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Revoke your unused setup link; revoke claimed connections through /keys/{keyId}","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"204":{"description":"Unused link revoked"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/agent-setups/claim":{"post":{"summary":"Agent self-registration: exchange a single-use setup token and chosen name for a scoped credential","description":"No browser session required. The setup token authorizes only the scope, permission, and lifetime chosen by the owner. Other input fields are rejected. Registration is atomic and cannot be repeated. The public guide at /agent-connect explains this flow; GET never consumes a link.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["setupToken","name"],"properties":{"setupToken":{"type":"string","pattern":"^pf_setup_[a-f0-9]{64}$"},"name":{"type":"string","minLength":1,"maxLength":80,"description":"Your chosen display name, without control characters"}}}}}},"responses":{"200":{"description":"Success"},"201":{"description":"id, name, token (shown once), createdAt, expiresAt, projectId (initial canvas), scope, permission, apiUrl, mcpUrl, mcpConfig, nextSteps"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"410":{"description":"Link expired, replaced, revoked, already claimed, or local workspace disabled"},"429":{"description":"Too many authentication attempts"}}}},"/keys":{"get":{"summary":"List your agent connections without secrets","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Create a scoped agent connection; token shown once","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"projectId":{"type":["string","null"],"description":"One owned canvas, or null for all accessible canvases"},"permission":{"enum":["read","comment","edit"],"default":"edit"},"expiresInDays":{"type":"integer","minimum":1,"maximum":90,"default":30}},"required":["name"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Agent connection and token"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/keys/{keyId}":{"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Revoke your agent connection","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"204":{"description":"Revoked"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/thumbnail":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Preview the first visible page as SVG","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/comments":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read comments and replies with author kind, page, position, status, timestamps, and comment revision","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Add a pinned comment","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000},"pageId":{"type":["string","null"],"description":"An existing root frame or null"},"x":{"type":"number","minimum":-100000,"maximum":100000},"y":{"type":"number","minimum":-100000,"maximum":100000}},"required":["body"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Comment created"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/comments/{commentId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Update comment status, or your own comment body, with the current comment revision","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["new","in_progress","handled"]},"body":{"type":"string","minLength":1,"maxLength":4000},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/comments/{commentId}/replies":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Reply to a comment thread","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000}},"required":["body"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Reply added"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/members":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List reviewers (canvas owner only)","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/members/{userId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Remove a reviewer (canvas owner only)","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"204":{"description":"Access removed"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/invitations":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List active invitations (canvas owner only)","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Create a single-use reviewer invitation, valid for seven days (canvas owner only)","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"enum":["viewer","commenter"]}},"required":["role"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Invitation and one-time token"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/invitations/{inviteId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"inviteId","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"summary":"Revoke an invitation (canvas owner only)","security":[{"browserSession":[]}],"responses":{"200":{"description":"Success"},"204":{"description":"Invitation revoked"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/invitations/accept":{"post":{"summary":"Accept a single-use invitation with a registered account","security":[{"browserSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/elements/{elementId}/nodes/{index}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"elementId","in":"path","required":true,"schema":{"type":"string"}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","minimum":0,"maximum":255}}],"patch":{"summary":"Edit one vector anchor (zero-based index)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."},"edit":{"oneOf":[{"type":"object","required":["action","position"],"properties":{"action":{"const":"move"},"position":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}}}},{"type":"object","required":["action"],"properties":{"action":{"const":"handles"},"in":{"anyOf":[{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}},{"type":"null"}]},"out":{"anyOf":[{"type":"object","required":["x","y"],"properties":{"x":{"type":"number","minimum":-20000,"maximum":20000},"y":{"type":"number","minimum":-20000,"maximum":20000}}},{"type":"null"}]}}},{"type":"object","required":["action"],"properties":{"action":{"enum":["smooth","corner","split","delete"]}}}]}},"required":["edit","revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects":{"get":{"summary":"List projects","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Create a project","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"template":{"enum":["blank","starter"]}},"required":["name"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Created"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read a complete project or inspect specific elements","description":"Agent reads appear in the activity feed without changing the canvas revision. When elementId is supplied, the returned elements array contains only the requested elements; parent frames may be omitted.","parameters":[{"name":"elementId","in":"query","description":"Repeat for each element to inspect, such as ?elementId=title&elementId=button. Omit to read the complete canvas.","schema":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"patch":{"summary":"Rename project","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["name","revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"delete":{"summary":"Delete a project","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["revision"]}}}},"responses":{"200":{"description":"Success"},"204":{"description":"Deleted"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/agent-activity":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read the latest 100 agent actions, newest first","description":"Requires canvas read access. Each action contains id, agentId, agentName, kind (inspect/edit/comment/export), message, at (ISO timestamp), status (completed/failed), and targets (element id/name snapshots). Feed reads do not record activity. SSE emits agent-activity events with projectId when this list changes; refresh the feed on that event. Activity has no effect on canvas revisions.","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/elements":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List elements","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"post":{"summary":"Add an element","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"element":{"$ref":"#/components/schemas/Element"},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["element","revision"]}}}},"responses":{"200":{"description":"Success"},"201":{"description":"Created"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/elements/{elementId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"elementId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read an element","responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"patch":{"summary":"Update element properties","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"changes":{"$ref":"#/components/schemas/ElementPatch"},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["changes","revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}},"delete":{"summary":"Delete element and descendants","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}},"required":["revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/operations":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Apply an atomic batch of up to 200 operations","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."},"operations":{"type":"array","minItems":1,"maxItems":200,"items":{"oneOf":[{"type":"object","required":["op","element"],"properties":{"op":{"const":"add"},"element":{"$ref":"#/components/schemas/Element"}}},{"type":"object","required":["op","id","changes"],"properties":{"op":{"const":"update"},"id":{"type":"string"},"changes":{"$ref":"#/components/schemas/ElementPatch"}}},{"type":"object","required":["op","id"],"properties":{"op":{"const":"delete"},"id":{"type":"string"}}},{"type":"object","required":["op","id","index"],"properties":{"op":{"const":"reorder"},"id":{"type":"string"},"index":{"type":"integer","minimum":0}}}]}}},"required":["operations","revision"]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/changes":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Synchronize every changed canvas element in one response","description":"Requires current read access. Send the revision held by the client. Returns 204 when unchanged; otherwise returns current design metadata, baseRevision, elements containing only changed/current elements, and elementOrder containing the complete current membership and order. Apply this atomically against baseRevision; omit elements absent from elementOrder. Revision is canvas-wide. This read does not append history.","parameters":[{"name":"revision","in":"query","required":true,"schema":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}}],"responses":{"200":{"description":"Success"},"204":{"description":"Canvas unchanged"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/restore":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Restore canvas elements from a stored revision","description":"Requires edit access. sourceRevision belongs to this file; revision must match the current live file. Restores artwork and layer order as a new edit while retaining the current name, reference links, and comments. Legacy bodies containing elements instead of sourceRevision remain accepted under the existing request-size limit.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["sourceRevision","revision"],"additionalProperties":false,"properties":{"sourceRevision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}}},{"type":"object","required":["elements","revision"],"additionalProperties":false,"properties":{"elements":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/Element"}},"revision":{"type":"integer","minimum":0,"description":"Expected revision; stale writes return 409."}}}]}}}},"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}},"/projects/{id}/export":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Export JSON or SVG","parameters":[{"name":"format","in":"query","schema":{"enum":["json","svg"]}}],"responses":{"200":{"description":"Success"},"400":{"description":"Invalid input"},"401":{"description":"Sign-in or agent key required"},"403":{"description":"Permission denied"},"404":{"description":"Not found"},"409":{"description":"Revision conflict"},"429":{"description":"Too many authentication attempts"}}}}}}