{"openapi":"3.0.3","info":{"title":"Doana API","version":"1.0.0","description":"Doana (DoanaIA) extracts EASA Form 1 and related aviation customs documents. The supported programmatic interface is Streamable HTTP MCP at /api/mcp with OAuth 2.1. There is no public REST mutation API besides MCP JSON-RPC and the health endpoint. Versioning: current major is v1 (X-Api-Version: 1, also published under /v1/openapi.json). Breaking changes ship as /v2. Deprecated operations receive Deprecation and Sunset headers for at least 90 days. Self-serve signup: /auth/sign-up. Agent index: /llms.txt. Developer onboarding: /developers.","contact":{"name":"Doana","email":"privacy@doana.ai","url":"https://doana.ca/contact"}},"externalDocs":{"description":"Deprecation and Sunset policy for Doana API v1","url":"https://doana.ca/deprecation"},"servers":[{"url":"https://doana.ca","description":"Production"}],"tags":[{"name":"Discovery","description":"Machine-readable site and API index"},{"name":"MCP","description":"Model Context Protocol Streamable HTTP"},{"name":"Health","description":"Liveness"}],"paths":{"/v1/openapi.json":{"get":{"operationId":"getOpenApiSpecV1","tags":["Discovery"],"summary":"Versioned OpenAPI specification","description":"Return the v1 OpenAPI 3.0 document. Same body as /openapi.json. Breaking changes will be published at /v2/openapi.json.","parameters":[{"name":"X-Api-Version","in":"header","required":false,"schema":{"type":"string","enum":["1"],"default":"1"},"description":"API major version. v1 is current."}],"responses":{"200":{"description":"OpenAPI document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApiSpec","tags":["Discovery"],"summary":"OpenAPI specification","description":"Return this OpenAPI 3.0 document so agents can discover Doana endpoints and MCP tools.","responses":{"200":{"description":"OpenAPI document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"}}}}}}},"/llms.txt":{"get":{"operationId":"getLlmsTxt","tags":["Discovery"],"summary":"Agent product index","description":"Return the curated llms.txt with when-to-use guidance, MCP instructions, and pages to cite.","responses":{"200":{"description":"Plain-text llms.txt","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/deprecation":{"get":{"operationId":"getDeprecationPolicy","tags":["Discovery"],"summary":"Deprecation and Sunset policy","description":"Return the v1 deprecation policy. Breaking changes ship as /v2. Deprecated operations send Deprecation and Sunset headers for at least 90 days. v1 is not deprecated.","responses":{"200":{"description":"Markdown deprecation policy","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/api/mcp/tools":{"get":{"operationId":"listMcpTools","tags":["MCP"],"summary":"Public MCP tool listing","description":"Return tool names, when-to-use guidance, and JSON Schema input shapes without authentication.","responses":{"200":{"description":"Public tool list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpToolList"}}}}}}},"/api":{"get":{"operationId":"getApiIndex","tags":["Discovery"],"summary":"API index","description":"Return JSON pointers to the OpenAPI spec, MCP transport, docs, and signup.","responses":{"200":{"description":"API index","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/api/healthcheck":{"get":{"operationId":"getHealthcheck","tags":["Health"],"summary":"Application health","description":"Return whether the web app and database are reachable. Used by uptime checks.","responses":{"200":{"description":"Health snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Healthcheck"}}}}}}},"/api/mcp":{"get":{"operationId":"openMcpSseStream","tags":["MCP"],"summary":"Open an MCP Streamable HTTP GET stream","description":"MCP Streamable HTTP GET. Requires a Bearer access token. Unauthenticated callers receive a JSON 401 with WWW-Authenticate.","security":[{"oauth2":["openid","offline_access"]}],"responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"403":{"description":"Token is valid but missing required scopes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"406":{"description":"GET requires Accept: text/event-stream for the MCP SSE stream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"409":{"description":"An SSE stream is already open for this session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}}}},"post":{"operationId":"callMcpJsonRpc","tags":["MCP"],"summary":"Call MCP JSON-RPC (Streamable HTTP)","description":"Send JSON-RPC 2.0 over Streamable HTTP. Methods: initialize, tools/list, tools/call, ping. Tools and their operationIds: create_mcp_upload, extract_easa_form1, get_extraction_result, get_usage. In ChatGPT, attach the document and pass it as the file param of extract_easa_form1. From any other client, for a file up to 64 KiB call extract_easa_form1 directly with content_base64 (lossless). Only for a larger file, or one that does not fit in a tool call, call create_mcp_upload, then either HTTP PUT the raw bytes to upload_url or, when the client has no outbound network, hand browser_upload_url to the user to upload from their browser; then extract_easa_form1 with upload_id. Do not send file:// or arbitrary https URLs.","security":[{"oauth2":["openid","offline_access"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}}},"responses":{"200":{"description":"JSON-RPC success or SSE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}},"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"403":{"description":"Token is valid but missing required scopes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"406":{"description":"POST requires Accept: application/json and text/event-stream","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"409":{"description":"An SSE stream is already open for this session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"415":{"description":"POST body must be application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}}}}}},"components":{"securitySchemes":{"oauth2":{"type":"oauth2","description":"Supabase OAuth 2.1 for the MCP resource. Discovery: /.well-known/oauth-protected-resource/api/mcp","flows":{"authorizationCode":{"authorizationUrl":"https://lfjrkupqsggmebszbmmi.supabase.co/auth/v1/oauth/authorize","tokenUrl":"https://lfjrkupqsggmebszbmmi.supabase.co/auth/v1/oauth/token","scopes":{"openid":"OpenID","offline_access":"Refresh token"}}}}},"schemas":{"Error":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"object","additionalProperties":false,"required":["code","message","hint"],"properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string"},"hint":{"type":"string"}}}}},"OAuthError":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","example":"invalid_token"},"error_description":{"type":"string"}}},"JsonRpcError":{"type":"object","additionalProperties":false,"required":["jsonrpc","error","id"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"error":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"integer","example":-32000},"message":{"type":"string"},"data":{"type":"object","additionalProperties":true}}},"id":{"oneOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]}}},"McpToolList":{"type":"object","additionalProperties":false,"required":["name","version","when_to_use","tools"],"properties":{"name":{"type":"string"},"version":{"type":"string"},"transport":{"type":"string"},"url":{"type":"string","format":"uri"},"authentication":{"type":"string"},"when_to_use":{"type":"string"},"tools":{"type":"array","items":{"type":"object","required":["operationId","name","description","inputSchema"],"properties":{"operationId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"inputSchema":{"type":"object","additionalProperties":true}}}}}},"OpenApiDocument":{"type":"object","additionalProperties":true,"required":["openapi","info","paths"],"properties":{"openapi":{"type":"string","example":"3.0.3"},"info":{"type":"object","additionalProperties":true},"paths":{"type":"object","additionalProperties":true}}},"ApiIndex":{"type":"object","additionalProperties":false,"required":["name","version","openapi","mcp","docs","signup"],"properties":{"name":{"type":"string","example":"Doana API"},"version":{"type":"string","example":"1"},"deprecation":{"type":"string"},"openapi":{"type":"string","format":"uri"},"mcp":{"type":"string","format":"uri"},"tools":{"type":"string","format":"uri"},"docs":{"type":"string","format":"uri"},"signup":{"type":"string","format":"uri"}}},"Healthcheck":{"type":"object","additionalProperties":false,"required":["services"],"properties":{"services":{"type":"object","additionalProperties":{"type":"boolean"},"properties":{"database":{"type":"boolean"}}}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"method":{"type":"string","description":"MCP JSON-RPC method, for example initialize, tools/list, tools/call"},"params":{"type":"object","additionalProperties":true}}},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"result":{"type":"object","additionalProperties":true},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"type":"object","additionalProperties":true}}}}},"McpFileInput":{"type":"object","additionalProperties":false,"required":["download_url","file_id"],"properties":{"download_url":{"type":"string","description":"Short-lived signed URL of the attachment, hosted by OpenAI. Filled by ChatGPT."},"file_id":{"type":"string","description":"OpenAI file id. Filled by ChatGPT."},"file_name":{"type":"string"},"mime_type":{"type":"string"}}},"CreateMcpUploadInput":{"type":"object","additionalProperties":false,"required":["file_name","mime_type","size_bytes"],"properties":{"file_name":{"type":"string","minLength":1,"maxLength":255},"mime_type":{"type":"string","enum":["application/pdf","image/jpeg","image/png"]},"size_bytes":{"type":"integer","minimum":1,"maximum":50000000,"description":"Per type: PDF up to 50 MB and 20 pages, JPEG or PNG up to 20 MB."}}},"ExtractEasaForm1Input":{"type":"object","additionalProperties":false,"properties":{"file":{"$ref":"#/components/schemas/McpFileInput"},"upload_id":{"type":"string","format":"uuid","description":"From create_mcp_upload, once the slot has been filled. Not used in ChatGPT."},"content_base64":{"type":"string","description":"Standard base64 of the original file bytes, lossless, for a file up to 64 KiB. Not used in ChatGPT."},"file_name":{"type":"string","description":"Original file name for upload_id or content_base64."},"mime_type":{"type":"string","description":"MIME type for upload_id or content_base64."},"idempotency_key":{"type":"string","minLength":1,"maxLength":128}},"oneOf":[{"required":["file"]},{"required":["upload_id"]},{"required":["content_base64"]}]},"GetExtractionResultInput":{"type":"object","additionalProperties":false,"required":["job_id"],"properties":{"job_id":{"type":"string","format":"uuid"}}},"GetUsageInput":{"type":"object","additionalProperties":false,"properties":{}}}},"x-mcp-tools":[{"operationId":"create_mcp_upload","name":"create_mcp_upload","description":"Not for ChatGPT: there, attach the file to extract_easa_form1 directly. For other clients, only for a file larger than 64 KiB or one that does not fit in a tool call; smaller files go to extract_easa_form1 with content_base64. Create an upload slot for one PDF, JPEG, or PNG (PDF up to 50 MB and 20 pages, JPEG or PNG up to 20 MB). Either HTTP PUT the raw binary bytes to upload_url with every required_headers entry, or give the user browser_upload_url to upload from their browser when this environment cannot make outbound requests. Then call extract_easa_form1 with upload_id.","inputSchema":{"$ref":"#/components/schemas/CreateMcpUploadInput"}},{"operationId":"extract_easa_form1","name":"extract_easa_form1","description":"Extract one EASA Form 1 from a PDF, JPEG, or PNG (PDF up to 50 MB and 20 pages, JPEG or PNG up to 20 MB). Pass exactly one source. In ChatGPT: attach the document and pass it as file. Elsewhere, default for a file up to 64 KiB: content_base64 of the original bytes, lossless. Only above 64 KiB, or when the file does not fit in a tool call, use upload_id from create_mcp_upload. Do not send file://, data:, gs:// or arbitrary https URLs.","inputSchema":{"$ref":"#/components/schemas/ExtractEasaForm1Input"}},{"operationId":"get_extraction_result","name":"get_extraction_result","description":"Read the current status and structured result of one EASA Form 1 job owned by the authenticated personal account.","inputSchema":{"$ref":"#/components/schemas/GetExtractionResultInput"}},{"operationId":"get_usage","name":"get_usage","description":"Return the authenticated personal account weekly EASA Form 1 quota.","inputSchema":{"$ref":"#/components/schemas/GetUsageInput"}}]}