{"openapi":"3.1.0","info":{"title":"Noddle Board","version":"0.1.0"},"paths":{"/api/config":{"get":{"summary":" Config","description":"Frontend feature flags: shared AI availability + Turnstile key.","operationId":"_config_api_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Config Api Config Get"}}}}}}},"/api/health":{"get":{"summary":" Health","operationId":"_health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Health Api Health Get"}}}}}}},"/api/documents":{"post":{"tags":["documents"],"summary":"Upload Document","operationId":"upload_document_api_documents_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_document_api_documents_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/new":{"post":{"tags":["documents"],"summary":"Create Document","description":"JSON create: blank board, template instance, or AI diagram.","operationId":"create_document_api_documents_new_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDocBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/import":{"post":{"tags":["documents"],"summary":"Import Document","description":"Import a foreign diagram file as a NEW editable board.\n\nSupported today: draw.io / diagrams.net (``.drawio`` / ``.xml`` — plain or\ndeflate-compressed, multi-page → noddle pages). Mermaid ``.mmd`` files are\nhandled client-side through the existing text→diagram AI path; ``.vsdx``\nis a spike (docs/spikes). A preview SVG is baked so exports aren't blank\nbefore the first open+save.","operationId":"import_document_api_documents_import_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_import_document_api_documents_import_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}":{"get":{"tags":["documents"],"summary":"Get Document","operationId":"get_document_api_documents__doc_id__get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["documents"],"summary":"Save Document","operationId":"save_document_api_documents__doc_id__put","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["documents"],"summary":"Patch Document","description":"Rename — needs \"edit\" (the only remaining meta knob).","operationId":"patch_document_api_documents__doc_id__patch","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchDocBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocMeta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}/export.svg":{"get":{"tags":["documents"],"summary":"Export Svg","operationId":"export_svg_api_documents__doc_id__export_svg_get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}/versions":{"get":{"tags":["documents"],"summary":"List Versions","description":"Snapshot metadata, newest first. Needs \"view\" (like reading the board).","operationId":"list_versions_api_documents__doc_id__versions_get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Versions Api Documents  Doc Id  Versions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}/versions/{version_id}":{"get":{"tags":["documents"],"summary":"Get Version","description":"One full snapshot (svg + diagram) for preview/restore. Restore itself is\nclient-driven: the frontend PUTs this payload back as a normal save.","operationId":"get_version_api_documents__doc_id__versions__version_id__get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Version Api Documents  Doc Id  Versions  Version Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}/comments":{"get":{"tags":["comments"],"summary":"List Comments","operationId":"list_comments_api_documents__doc_id__comments_get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["comments"],"summary":"Create Comment","operationId":"create_comment_api_documents__doc_id__comments_post","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/documents/{doc_id}/comments/{comment_id}":{"patch":{"tags":["comments"],"summary":"Patch Comment","operationId":"patch_comment_api_documents__doc_id__comments__comment_id__patch","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}},{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","title":"Comment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["comments"],"summary":"Delete Comment","operationId":"delete_comment_api_documents__doc_id__comments__comment_id__delete","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}},{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","title":"Comment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai/test-key":{"post":{"tags":["ai"],"summary":"Test Ai Key","description":"Fire the smallest possible chat at the caller's provider to prove the\nkey/model/base combination works. Always resolves ``{ok, message}`` — a\nbad key is ``ok: false``, never a throw (only malformed headers are 400).\nThe key rides the X-AI-* headers like every other AI call; it is neither\nstored nor logged.","operationId":"test_ai_key_api_ai_test_key_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Test Ai Key Api Ai Test Key Post"}}}}}}},"/api/ai/image-to-svg":{"post":{"tags":["ai"],"summary":"Image To Svg","operationId":"image_to_svg_api_ai_image_to_svg_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_image_to_svg_api_ai_image_to_svg_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SvgOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai/jobs/image-to-svg":{"post":{"tags":["ai"],"summary":"Submit Image Job","operationId":"submit_image_job_api_ai_jobs_image_to_svg_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_image_job_api_ai_jobs_image_to_svg_post"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Image Job Api Ai Jobs Image To Svg Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai/jobs":{"get":{"tags":["ai"],"summary":"List Image Jobs","operationId":"list_image_jobs_api_ai_jobs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Image Jobs Api Ai Jobs Get"}}}}}}},"/api/ai/jobs/{job_id}":{"get":{"tags":["ai"],"summary":"Get Image Job","operationId":"get_image_job_api_ai_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Image Job Api Ai Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["ai"],"summary":"Delete Image Job","operationId":"delete_image_job_api_ai_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Image Job Api Ai Jobs  Job Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai/text-to-diagram":{"post":{"tags":["ai"],"summary":"Text To Diagram","operationId":"text_to_diagram_api_ai_text_to_diagram_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagramBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Text To Diagram Api Ai Text To Diagram Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai/edit-diagram":{"post":{"tags":["ai"],"summary":"Edit Diagram","description":"Live co-editing: apply a chat instruction to the current board.\n\nAn optional ``model`` in the body selects the Databricks serving-endpoint\nfor this chat session (pool mode only); the service whitelists it (must\nstart with ``databricks-``) and otherwise falls back to the default\nendpoint.","operationId":"edit_diagram_api_ai_edit_diagram_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditDiagramBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Edit Diagram Api Ai Edit Diagram Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":" Spa Root","operationId":"_spa_root__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/d/{doc_id}":{"get":{"summary":" Spa Document","operationId":"_spa_document_d__doc_id__get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/{doc_id}":{"get":{"summary":" Spa Embed","description":"Read-only iframe view — the SPA hides all chrome for /embed/*.\nThis route is exempt from the frame-ancestors lockdown above.","operationId":"_spa_embed_embed__doc_id__get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/generate":{"get":{"summary":" Spa Generate","operationId":"_spa_generate_generate_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"Body_image_to_svg_api_ai_image_to_svg_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"prompt":{"type":"string","title":"Prompt","default":""}},"type":"object","required":["file"],"title":"Body_image_to_svg_api_ai_image_to_svg_post"},"Body_import_document_api_documents_import_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_import_document_api_documents_import_post"},"Body_submit_image_job_api_ai_jobs_image_to_svg_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"prompt":{"type":"string","title":"Prompt","default":""}},"type":"object","required":["file"],"title":"Body_submit_image_job_api_ai_jobs_image_to_svg_post"},"Body_upload_document_api_documents_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_document_api_documents_post"},"CommentIn":{"properties":{"body":{"type":"string","title":"Body"},"page_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Id"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"anchor":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Anchor"},"guest_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Name"},"guest_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Color"}},"type":"object","required":["body"],"title":"CommentIn"},"CommentOut":{"properties":{"id":{"type":"string","title":"Id"},"body":{"type":"string","title":"Body"},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id"},"author_name":{"type":"string","title":"Author Name"},"author_color":{"type":"string","title":"Author Color"},"page_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Id"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"anchor":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Anchor"},"resolved":{"type":"boolean","title":"Resolved"},"created_at":{"type":"number","title":"Created At"},"updated_at":{"type":"number","title":"Updated At"}},"type":"object","required":["id","body","author_id","author_name","author_color","page_id","parent_id","anchor","resolved","created_at","updated_at"],"title":"CommentOut"},"CommentPatch":{"properties":{"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"resolved":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Resolved"}},"type":"object","title":"CommentPatch"},"CommentsOut":{"properties":{"comments":{"items":{"$ref":"#/components/schemas/CommentOut"},"type":"array","title":"Comments"}},"type":"object","required":["comments"],"title":"CommentsOut"},"CreateDocBody":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"svg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Svg"},"diagram":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Diagram"}},"type":"object","title":"CreateDocBody","description":"Request body for POST /api/documents/new (blank board / template / AI)."},"DiagramBody":{"properties":{"text":{"type":"string","title":"Text"},"format":{"type":"string","enum":["text","mermaid"],"title":"Format","default":"text"}},"type":"object","required":["text"],"title":"DiagramBody","description":"Request body for POST /api/ai/text-to-diagram."},"DocMeta":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"number","title":"Created At"},"updated_at":{"type":"number","title":"Updated At"},"link_policy":{"type":"string","title":"Link Policy","default":"edit"}},"type":"object","required":["id","name","created_at","updated_at"],"title":"DocMeta","description":"Document metadata as returned to clients."},"DocumentOut":{"properties":{"meta":{"$ref":"#/components/schemas/DocMeta"},"svg":{"type":"string","title":"Svg"},"diagram":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Diagram"},"my_role":{"type":"string","title":"My Role","default":"editor"}},"type":"object","required":["meta","svg"],"title":"DocumentOut","description":"Full document (metadata + sanitized SVG + optional editable diagram).\n\n``my_role`` is the CALLER's effective role (editor|viewer) so the frontend\ncan lock the UI without re-deriving the link policy client-side."},"EditDiagramBody":{"properties":{"instruction":{"type":"string","title":"Instruction"},"diagram":{"additionalProperties":true,"type":"object","title":"Diagram"},"history":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"History","default":[]},"mode":{"type":"string","enum":["edit","ask"],"title":"Mode","default":"edit"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image"}},"type":"object","required":["instruction","diagram"],"title":"EditDiagramBody","description":"Request body for POST /api/ai/edit-diagram (live co-editing).\n\n``history`` is the recent conversation (role user|assistant, text only —\nthe CURRENT diagram is always sent fresh, never stale snapshots), so\nfollow-ups like \"change it to blue\" resolve their referents."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PatchDocBody":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","title":"PatchDocBody","description":"PATCH /api/documents/{id}: rename (the only meta knob)."},"SaveBody":{"properties":{"svg":{"type":"string","title":"Svg"},"diagram":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Diagram"},"author_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Name"}},"type":"object","required":["svg"],"title":"SaveBody","description":"Request body for PUT /api/documents/{id}.\n\n``diagram`` semantics: omitted → keep stored; null → clear; object → replace.\n``author_name`` attributes the version snapshot (client-side identity)."},"SvgOut":{"properties":{"svg":{"type":"string","title":"Svg"}},"type":"object","required":["svg"],"title":"SvgOut","description":"Response for POST /api/ai/image-to-svg."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}