{"openapi":"3.0.3","info":{"title":"Oxynet CPET API","version":"v1","description":"Analyse cardiopulmonary exercise tests (CPET). Upload a vendor file, get a cpet_id, then analyse it by that id.\n\nDo not parse the file or convert its units yourself — Oxynet detects the format and handles the unit conventions. Upload the vendor export unchanged.\n\nResults carry no confidence score; nothing here is calibrated against clinical outcomes. Oxynet is research software and its output is not a diagnosis."},"servers":[{"url":"https://app.oxynet.net"}],"paths":{"/v1/cpet/content":{"post":{"tags":["v1"],"summary":"Upload a CPET recording as JSON (no multipart)","description":"Same as POST /v1/cpet, with the file inlined in a JSON body.\n\nUse this from a platform that cannot send multipart form data —\nChatGPT Actions and Gemini function calling both fall into that\ncategory. Everything else is identical: the bytes are parsed and\ndiscarded, and you get back a `cpet_id`.\n\nPut text formats in `content` and binary ones (XLS/XLSX) in\n`content_base64`. **`filename` must keep its real extension** — it is\nwhat selects the parser.","operationId":"uploadCpetJson","requestBody":{"content":{"application/json":{"schema":{"properties":{"filename":{"type":"string","title":"Filename","description":"Original filename WITH its extension — the extension selects the parser"},"content":{"type":"string","title":"Content","description":"File text, for CSV/TXT/XML/JSON","nullable":true},"content_base64":{"type":"string","title":"Content Base64","description":"Base64 bytes, for XLS/XLSX","nullable":true},"retain_hours":{"type":"integer","title":"Retain Hours","nullable":true}},"additionalProperties":false,"type":"object","required":["filename"],"title":"UploadJSONRequest","description":"JSON-body upload, for callers that cannot send multipart.\n\nChatGPT Custom GPT Actions and Gemini function calling both emit JSON only,\nso `POST /v1/cpet` — a multipart form — is unreachable from either. This is\nthe same operation with the file inlined.\n\nText formats (CSV, TXT, XML, JSON) go in `content`. Binary ones (XLS, XLSX)\nmust be base64 in `content_base64`, because JSON cannot carry raw bytes."}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/uploads":{"post":{"tags":["v1"],"summary":"Get a one-shot URL to POST a file to","description":"A short-lived URL that accepts one file upload, no API key needed.\n\nThis exists because inlining is the wrong answer for a file already on\ndisk. An agent holding a 360 KB export and talking to a hosted server\nhas to send the bytes through its own context — roughly 90,000 tokens\nfor one recording, which is precisely the cost the handle design was\nbuilt to avoid. With a ticket the agent runs one shell command, the\nbytes go straight from disk to the server, and only the `cpet_id` comes\nback into the conversation.\n\nThe URL carries a signed ticket binding it to this key and expiring in\nfive minutes, so it is not a credential worth capturing and cannot be\npointed at another tenant's storage.","operationId":"createUploadTicket","requestBody":{"content":{"application/json":{"schema":{"properties":{"filename":{"type":"string","title":"Filename","nullable":true},"retain_hours":{"type":"integer","title":"Retain Hours","nullable":true}},"additionalProperties":false,"type":"object","title":"UploadTicketRequest","description":"Ask for a one-shot URL to POST a file at."}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/cpet/{cpet_id}":{"get":{"tags":["v1"],"summary":"Summary of a stored recording","operationId":"getCpet","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"cpet_id","in":"path","required":true,"schema":{"type":"string","title":"Cpet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}},"delete":{"tags":["v1"],"summary":"Delete a stored recording now","operationId":"deleteCpet","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"cpet_id","in":"path","required":true,"schema":{"type":"string","title":"Cpet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}}},"/v1/cpet/{cpet_id}/series":{"get":{"tags":["v1"],"summary":"Downsampled series, for plotting","description":"Subsample of the stored series.\n\nFor plotting and for inspecting a specific stretch — **not** for\nanalysis. Analysis runs server-side on the full record; pulling a series\nthrough an agent's context to compute on it is the pattern this API\nexists to remove.","operationId":"getCpetSeries","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"cpet_id","in":"path","required":true,"schema":{"type":"string","title":"Cpet Id"}},{"name":"channels","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated, e.g. VE,PetCO2","default":"VE","title":"Channels"},"description":"Comma-separated, e.g. VE,PetCO2"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":10,"default":500,"title":"Max Points"}},{"name":"grid","in":"query","required":false,"schema":{"type":"string","pattern":"^(raw|resampled)$","default":"raw","title":"Grid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}}},"/v1/cpet/{cpet_id}/analyze":{"post":{"tags":["v1"],"summary":"Run models on a stored recording","description":"Run one or more analyses on a stored recording.\n\nEach analysis returns its own envelope with its own provenance, status\nand notes. One failing does not fail the others — an agent asking for\nboth VT and EOV on a file with no PetCO2 should get its thresholds and a\nstated reason for the missing oscillation result.","operationId":"analyzeCpet","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"cpet_id","in":"path","required":true,"schema":{"type":"string","title":"Cpet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"analyses":{"items":{"type":"string"},"type":"array","title":"Analyses","description":"Any of: vt, eov"},"model":{"type":"string","title":"Model","description":"Threshold model. Omit for the server default, which matches the web app.","default":"gandalf"},"model_version":{"type":"string","title":"Model Version","default":"latest"},"medications":{"items":{"type":"string"},"type":"array","title":"Medications","description":"EOV burden adjustment; all factors currently ship at 1.0"}},"additionalProperties":false,"type":"object","title":"AnalyzeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}}},"/v1/metrics":{"get":{"tags":["v1"],"summary":"The CPET function registry","description":"Every function callable through POST /v1/cpet/{id}/compute.\n\nEach entry states what it returns, its unit, the channels it needs, and\nthe traps in reading it. Enumerate this rather than guessing names.","operationId":"listMetrics","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/cpet/{cpet_id}/compute":{"post":{"tags":["v1"],"summary":"Run registry functions","description":"Compute derived quantities on a stored recording.\n\nA metric whose preconditions are unmet is returned with `status:\n\"unavailable\"` and a reason. It is never computed on inadequate input\nand never returned as a bare null.","operationId":"computeMetrics","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"cpet_id","in":"path","required":true,"schema":{"type":"string","title":"Cpet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"properties":{"metrics":{"items":{"type":"string"},"type":"array","title":"Metrics","description":"Names from GET /v1/metrics"}},"additionalProperties":false,"type":"object","required":["metrics"],"title":"ComputeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}}},"/v1/capabilities":{"get":{"tags":["v1"],"summary":"What this key can do","description":"Entry point for an agent: products, models, metrics and limits.","operationId":"getCapabilities","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/v1/sample":{"get":{"tags":["v1"],"summary":"A synthetic CPET you can analyse immediately","description":"A synthetic recording, for demonstrating the API end to end.\n\n**Use this instead of a real patient file when showing how Oxynet\nworks.** Upload the returned object to `/v1/cpet/content` as `content`\nwith `filename: \"oxynet_sample.json\"`, then analyse it like any other\nrecording. Nothing here comes from a person.\n\nReal CPET exports are health data, and their filenames routinely carry\npatient names. Reaching for one to prove an API responds is out of\nproportion to the question — and agent runtimes are right to refuse it.\n\nSet `oscillating=true` for a recording that exercises the oscillatory\nbreathing detector. The thresholds in the plain sample sit near 55 % and\n80 % of the exercise period **because they were placed there**; this is\na demonstration fixture, not validation data.","operationId":"getSampleCpet","parameters":[{"name":"oscillating","in":"query","required":false,"schema":{"type":"boolean","description":"Include an exercise oscillatory ventilation pattern","default":false,"title":"Oscillating"},"description":"Include an exercise oscillatory ventilation pattern"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"properties":{"detail":{"items":{"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"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"}}}}}}},"/v1/formats":{"get":{"tags":["v1"],"summary":"Supported metabolimeter formats","operationId":"listFormats","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}}},"components":{"securitySchemes":{"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}},"security":[{"APIKeyHeader":[]}]}