{"openapi":"3.1.0","info":{"title":"Optical Diagnostics API","description":"AI-powered eye diagnostic services (text, image, multimodal) secured by API keys. Outputs are clinical decision-support only — clinicians retain final decision authority.","version":"0.1.0"},"paths":{"/api/v1/auth/request-access":{"post":{"tags":["API keys"],"summary":"Request Access","description":"Step 1–2 of the key lifecycle: consumer registers and requests access.","operationId":"request_access_api_v1_auth_request_access_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/requests":{"get":{"tags":["API keys"],"summary":"List Access Requests","description":"Step 3: administrator lists access requests awaiting review.","operationId":"list_access_requests_api_v1_auth_requests_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(pending|approved|rejected)$"},{"type":"null"}],"title":"Status"}},{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AccessRequestDetail"},"title":"Response List Access Requests Api V1 Auth Requests Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/requests/{request_id}/approve":{"post":{"tags":["API keys"],"summary":"Approve Access Request","description":"Step 4–5: approve the request and issue a key (plaintext shown once).","operationId":"approve_access_request_api_v1_auth_requests__request_id__approve_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}},{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AccessRequestApprove"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/requests/{request_id}/reject":{"post":{"tags":["API keys"],"summary":"Reject Access Request","description":"Reject an access request with an optional reason (audit-logged).","operationId":"reject_access_request_api_v1_auth_requests__request_id__reject_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}},{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AccessRequestReject"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/keys":{"post":{"tags":["API keys"],"summary":"Create Key","description":"Step 4–5: administrator approves and generates a key (shown once).\n\nIssuing a key directly also resolves the consumer's oldest pending\naccess request, so the review queue stays consistent.","operationId":"create_key_api_v1_auth_keys_post","parameters":[{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/keys/rotate":{"post":{"tags":["API keys"],"summary":"Rotate Key","description":"Revoke the old key and issue a replacement with the same settings.","operationId":"rotate_key_api_v1_auth_keys_rotate_post","parameters":[{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyActionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/keys/revoke":{"post":{"tags":["API keys"],"summary":"Revoke Key","operationId":"revoke_key_api_v1_auth_keys_revoke_post","parameters":[{"name":"x-admin-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyActionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/inference/text":{"post":{"tags":["Inference"],"summary":"Diagnose Text","description":"Text diagnosis — served by the ANN-LSTM model unless `model` overrides it.","operationId":"diagnose_text_api_v1_inference_text_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextDiagnosisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnosisResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/inference/image":{"post":{"tags":["Inference"],"summary":"Diagnose Image","operationId":"diagnose_image_api_v1_inference_image_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_diagnose_image_api_v1_inference_image_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnosisResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/inference/multimodal":{"post":{"tags":["Inference"],"summary":"Diagnose Multimodal","operationId":"diagnose_multimodal_api_v1_inference_multimodal_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_diagnose_multimodal_api_v1_inference_multimodal_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiagnosisResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/analytics/usage":{"get":{"tags":["Analytics"],"summary":"Usage Summary","description":"Aggregate request counts and latency for the calling key.","operationId":"usage_summary_api_v1_analytics_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/models":{"get":{"tags":["Models"],"summary":"List Models","operationId":"list_models_api_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/uploads":{"get":{"tags":["Uploads"],"summary":"List Uploads","description":"List the calling consumer's stored objects.","operationId":"list_uploads_api_v1_uploads_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/StoredObjectResponse"},"type":"array","title":"Response List Uploads Api V1 Uploads Get"}}}}},"security":[{"APIKeyHeader":[]}]},"post":{"tags":["Uploads"],"summary":"Upload File","description":"Store a medical image; returns the object key and a presigned URL.","operationId":"upload_file_api_v1_uploads_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_file_api_v1_uploads_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/uploads/{object_key}/url":{"get":{"tags":["Uploads"],"summary":"Presign Upload","description":"Mint a fresh presigned GET URL for one of the consumer's objects.","operationId":"presign_upload_api_v1_uploads__object_key__url_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"object_key","in":"path","required":true,"schema":{"type":"string","title":"Object Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/uploads/{object_key}":{"delete":{"tags":["Uploads"],"summary":"Delete Upload","description":"Delete one of the consumer's stored objects.","operationId":"delete_upload_api_v1_uploads__object_key__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"object_key","in":"path","required":true,"schema":{"type":"string","title":"Object Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List Webhooks","description":"List the calling consumer's registered webhooks (secrets omitted).","operationId":"list_webhooks_api_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WebhookResponse"},"type":"array","title":"Response List Webhooks Api V1 Webhooks Get"}}}}},"security":[{"APIKeyHeader":[]}]},"post":{"tags":["Webhooks"],"summary":"Register Webhook","description":"Register a callback URL for the calling key's consumer.","operationId":"register_webhook_api_v1_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/webhooks/{webhook_id}":{"delete":{"tags":["Webhooks"],"summary":"Delete Webhook","description":"Delete one of the calling consumer's webhooks.","operationId":"delete_webhook_api_v1_webhooks__webhook_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AccessRequestApprove":{"properties":{"label":{"type":"string","title":"Label","default":"default"},"environment":{"type":"string","pattern":"^(live|test)$","title":"Environment","default":"live"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":["inference:text","inference:image","inference:multimodal","uploads:read","uploads:write"]},"expires_in_days":{"anyOf":[{"type":"integer","maximum":730.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days"}},"type":"object","title":"AccessRequestApprove","description":"Key parameters applied when an admin approves an access request."},"AccessRequestCreate":{"properties":{"organization":{"type":"string","maxLength":255,"minLength":2,"title":"Organization"},"contact_name":{"type":"string","maxLength":255,"minLength":2,"title":"Contact Name"},"contact_email":{"type":"string","format":"email","title":"Contact Email"},"purpose":{"type":"string","minLength":10,"title":"Purpose","description":"What the API will be used for"}},"type":"object","required":["organization","contact_name","contact_email","purpose"],"title":"AccessRequestCreate"},"AccessRequestDetail":{"properties":{"id":{"type":"string","title":"Id"},"organization":{"type":"string","title":"Organization"},"contact_name":{"type":"string","title":"Contact Name"},"contact_email":{"type":"string","format":"email","title":"Contact Email"},"purpose":{"type":"string","title":"Purpose"},"status":{"type":"string","title":"Status"},"reviewed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By"},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","organization","contact_name","contact_email","purpose","status","reviewed_by","reviewed_at","created_at"],"title":"AccessRequestDetail","description":"Admin review view of an access request (SRD §6 lifecycle step 3–4)."},"AccessRequestReject":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason"}},"type":"object","title":"AccessRequestReject"},"AccessRequestResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["request_id","status","message"],"title":"AccessRequestResponse"},"Body_diagnose_image_api_v1_inference_image_post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image"},"image_type":{"type":"string","title":"Image Type","default":"fundus"}},"type":"object","required":["image"],"title":"Body_diagnose_image_api_v1_inference_image_post"},"Body_diagnose_multimodal_api_v1_inference_multimodal_post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image"},"clinical_data":{"type":"string","title":"Clinical Data","description":"JSON-encoded TextDiagnosisRequest"}},"type":"object","required":["image","clinical_data"],"title":"Body_diagnose_multimodal_api_v1_inference_multimodal_post"},"Body_upload_file_api_v1_uploads_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_file_api_v1_uploads_post"},"DiagnosisResponse":{"properties":{"prediction":{"$ref":"#/components/schemas/Prediction"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"treatment_recommendation":{"type":"string","title":"Treatment Recommendation"},"disclaimer":{"type":"string","title":"Disclaimer","default":"This output is an AI-generated clinical decision-support suggestion, not a medical diagnosis. A qualified clinician must review all findings and retains final decision authority."},"explanation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Explanation"},"image_object_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Object Key"}},"type":"object","required":["prediction","confidence","treatment_recommendation"],"title":"DiagnosisResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"KeyActionRequest":{"properties":{"key_id":{"type":"string","title":"Key Id"}},"type":"object","required":["key_id"],"title":"KeyActionRequest"},"KeyCreateRequest":{"properties":{"consumer_email":{"type":"string","format":"email","title":"Consumer Email"},"label":{"type":"string","title":"Label","default":"default"},"environment":{"type":"string","pattern":"^(live|test)$","title":"Environment","default":"live"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":["inference:text","inference:image","inference:multimodal","uploads:read","uploads:write"]},"expires_in_days":{"anyOf":[{"type":"integer","maximum":730.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days"}},"type":"object","required":["consumer_email"],"title":"KeyCreateRequest"},"KeyResponse":{"properties":{"key_id":{"type":"string","title":"Key Id"},"api_key":{"type":"string","title":"Api Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"environment":{"type":"string","title":"Environment"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"warning":{"type":"string","title":"Warning","default":"Store this key securely now. It is shown only once and cannot be retrieved."}},"type":"object","required":["key_id","api_key","key_prefix","environment","scopes","expires_at"],"title":"KeyResponse","description":"Returned ONCE at creation/rotation — plaintext is never stored."},"Prediction":{"properties":{"condition":{"type":"string","title":"Condition"},"probabilities":{"additionalProperties":{"type":"number"},"type":"object","title":"Probabilities"},"model_version":{"type":"string","title":"Model Version"}},"type":"object","required":["condition","probabilities","model_version"],"title":"Prediction"},"PresignedUrlResponse":{"properties":{"object_key":{"type":"string","title":"Object Key"},"url":{"type":"string","title":"Url"},"url_expires_in_seconds":{"type":"integer","title":"Url Expires In Seconds"}},"type":"object","required":["object_key","url","url_expires_in_seconds"],"title":"PresignedUrlResponse"},"StoredObjectResponse":{"properties":{"object_key":{"type":"string","title":"Object Key"},"size":{"type":"integer","title":"Size"},"last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Modified"}},"type":"object","required":["object_key","size"],"title":"StoredObjectResponse"},"TextDiagnosisRequest":{"properties":{"age":{"type":"integer","maximum":130.0,"minimum":0.0,"title":"Age"},"symptoms":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Symptoms"},"laboratory_results":{"additionalProperties":{"type":"string"},"type":"object","title":"Laboratory Results"},"medical_history":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Medical History"},"family_history":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Family History"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Text model to run. Defaults to 'ann-lstm'. One of: ann-lstm, ann, lstm, gru, xgboost-gru."}},"type":"object","required":["age","symptoms"],"title":"TextDiagnosisRequest","description":"Input features for the ANN-LSTM text model (SRD §9)."},"UploadResponse":{"properties":{"object_key":{"type":"string","title":"Object Key"},"content_type":{"type":"string","title":"Content Type"},"size":{"type":"integer","title":"Size"},"url":{"type":"string","title":"Url"},"url_expires_in_seconds":{"type":"integer","title":"Url Expires In Seconds"}},"type":"object","required":["object_key","content_type","size","url","url_expires_in_seconds"],"title":"UploadResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookCreate":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url"},"event_types":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Event Types","description":"e.g. [\"inference.completed\", \"key.expiring\"]"},"secret":{"type":"string","maxLength":128,"minLength":16,"title":"Secret","description":"Shared secret used to HMAC-sign deliveries"}},"type":"object","required":["url","event_types","secret"],"title":"WebhookCreate"},"WebhookResponse":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","url","event_types","is_active","created_at"],"title":"WebhookResponse","description":"Registered webhook — the signing secret is never returned."}},"securitySchemes":{"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}