{"openapi":"3.1.0","info":{"title":"DocTransform API","version":"1"},"servers":[{"url":"https://doctransform.xyz"}],"paths":{"/api/v1/templates":{"get":{"tags":["public-api"],"summary":"List Templates","operationId":"list_templates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListResponse"}}}},"401":{"description":"API_KEY_MISSING, API_KEY_INVALID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"API_KEY_MISSING","message":"Send the API Key as an `Authorization: Bearer` header."},{"code":"API_KEY_INVALID","message":"The API Key is malformed, unknown, revoked, or its account is inactive."}]},"403":{"description":"INSUFFICIENT_SCOPE, NO_SUBSCRIPTION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"INSUFFICIENT_SCOPE","message":"The API Key does not carry the scope this operation requires."},{"code":"NO_SUBSCRIPTION","message":"This account has no active subscription."}]},"429":{"description":"CONCURRENCY_LIMIT_REACHED, RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"CONCURRENCY_LIMIT_REACHED","message":"This account already has the maximum number of Runs in flight for its plan."},{"code":"RATE_LIMITED","message":"This account has exceeded its request rate limit for this operation."}]}},"security":[{"bearerAuth":["templates:read"]}]}},"/api/v1/templates/{template_id}/runs":{"post":{"tags":["public-api"],"summary":"Create Run","description":"Runs `template_id` on one uploaded file. Answers `201` with a\nfinished Run if it completes within about 30 seconds, else `202` with\n`Location`/`Retry-After` while it keeps running in the background --\npoll `GET /runs/{id}` for its outcome.\n\nAn optional `Idempotency-Key` header makes a retry of the same\nrequest replay the Run it already started or finished, rather than\nstarting (and charging) a second one; the same key with a different\nrequest answers `409`.","operationId":"create_run","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Replays the Run already started or finished for this key rather than starting a second one; a different request with the same key answers `409 IDEMPOTENCY_KEY_REUSED`."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunResponse"}}}},"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunResponse"}}},"description":"OK"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunResponse"}}},"description":"Accepted"},"400":{"description":"MALFORMED_FILE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"MALFORMED_FILE","message":"The file could not be read: it is empty, corrupt, or not encoded as expected."}]},"401":{"description":"API_KEY_MISSING, API_KEY_INVALID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"API_KEY_MISSING","message":"Send the API Key as an `Authorization: Bearer` header."},{"code":"API_KEY_INVALID","message":"The API Key is malformed, unknown, revoked, or its account is inactive."}]},"402":{"description":"EXPORT_LIMIT_REACHED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaErrorBody"}}},"x-error-codes":[{"code":"EXPORT_LIMIT_REACHED","message":"This account has used its Quota for the current billing period."}]},"403":{"description":"INSUFFICIENT_SCOPE, NO_SUBSCRIPTION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"INSUFFICIENT_SCOPE","message":"The API Key does not carry the scope this operation requires."},{"code":"NO_SUBSCRIPTION","message":"This account has no active subscription."}]},"404":{"description":"TEMPLATE_NOT_FOUND, RUN_NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"TEMPLATE_NOT_FOUND","message":"No Template with this id exists for this account."},{"code":"RUN_NOT_FOUND","message":"No Run with this id exists for this account. Runs are kept 30 minutes and are lost if the service restarts."}]},"409":{"description":"IDEMPOTENCY_KEY_REUSED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"IDEMPOTENCY_KEY_REUSED","message":"This `Idempotency-Key` was already used for a different request."}]},"413":{"description":"FILE_TOO_LARGE, ROW_LIMIT_EXCEEDED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"FILE_TOO_LARGE","message":"The uploaded file exceeds this plan's upload size ceiling."},{"code":"ROW_LIMIT_EXCEEDED","message":"The uploaded file exceeds this plan's row ceiling."}]},"415":{"description":"UNSUPPORTED_FILE_TYPE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"UNSUPPORTED_FILE_TYPE","message":"This file type is not supported."}]},"422":{"description":"INVALID_PARSE_PARAMS, INVALID_IDEMPOTENCY_KEY, TEMPLATE_COLUMNS_MISSING","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"INVALID_PARSE_PARAMS","message":"`delimiter` must be one printable character; `header_row` must be a non-negative integer within the row ceiling."},{"code":"INVALID_IDEMPOTENCY_KEY","message":"`Idempotency-Key` must be 1-255 printable characters."},{"code":"TEMPLATE_COLUMNS_MISSING","message":"The uploaded file is missing one or more columns this Template's operations require."}]},"429":{"description":"CONCURRENCY_LIMIT_REACHED, RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"CONCURRENCY_LIMIT_REACHED","message":"This account already has the maximum number of Runs in flight for its plan."},{"code":"RATE_LIMITED","message":"This account has exceeded its request rate limit for this operation."}]},"503":{"description":"SERVER_BUSY","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"SERVER_BUSY","message":"This worker is at its memory budget; retry shortly."}]}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"delimiter":{"type":"string"},"header_row":{"type":"integer"}},"required":["file"]}}}},"security":[{"bearerAuth":["runs:write"]}]}},"/api/v1/runs/{run_id}":{"get":{"tags":["public-api"],"summary":"Get Run","operationId":"get_run","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunResponse"}}}},"401":{"description":"API_KEY_MISSING, API_KEY_INVALID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"API_KEY_MISSING","message":"Send the API Key as an `Authorization: Bearer` header."},{"code":"API_KEY_INVALID","message":"The API Key is malformed, unknown, revoked, or its account is inactive."}]},"403":{"description":"INSUFFICIENT_SCOPE, NO_SUBSCRIPTION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"INSUFFICIENT_SCOPE","message":"The API Key does not carry the scope this operation requires."},{"code":"NO_SUBSCRIPTION","message":"This account has no active subscription."}]},"404":{"description":"TEMPLATE_NOT_FOUND, RUN_NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"TEMPLATE_NOT_FOUND","message":"No Template with this id exists for this account."},{"code":"RUN_NOT_FOUND","message":"No Run with this id exists for this account. Runs are kept 30 minutes and are lost if the service restarts."}]},"429":{"description":"CONCURRENCY_LIMIT_REACHED, RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"CONCURRENCY_LIMIT_REACHED","message":"This account already has the maximum number of Runs in flight for its plan."},{"code":"RATE_LIMITED","message":"This account has exceeded its request rate limit for this operation."}]}},"security":[{"bearerAuth":["runs:read"]}]}},"/api/v1/runs/{run_id}/output":{"get":{"tags":["public-api"],"summary":"Get Run Output","operationId":"get_run_output","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"API_KEY_MISSING, API_KEY_INVALID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"API_KEY_MISSING","message":"Send the API Key as an `Authorization: Bearer` header."},{"code":"API_KEY_INVALID","message":"The API Key is malformed, unknown, revoked, or its account is inactive."}]},"403":{"description":"INSUFFICIENT_SCOPE, NO_SUBSCRIPTION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"INSUFFICIENT_SCOPE","message":"The API Key does not carry the scope this operation requires."},{"code":"NO_SUBSCRIPTION","message":"This account has no active subscription."}]},"404":{"description":"TEMPLATE_NOT_FOUND, RUN_NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"TEMPLATE_NOT_FOUND","message":"No Template with this id exists for this account."},{"code":"RUN_NOT_FOUND","message":"No Run with this id exists for this account. Runs are kept 30 minutes and are lost if the service restarts."}]},"429":{"description":"CONCURRENCY_LIMIT_REACHED, RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}},"x-error-codes":[{"code":"CONCURRENCY_LIMIT_REACHED","message":"This account already has the maximum number of Runs in flight for its plan."},{"code":"RATE_LIMITED","message":"This account has exceeded its request rate limit for this operation."}]}},"security":[{"bearerAuth":["runs:read"]}]}}},"components":{"schemas":{"RunErrorBody":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"}},"type":"object","required":["code","message"],"title":"RunErrorBody"},"TemplateListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/app__domains__public_api__schemas__TemplateListItem"},"type":"array","title":"Data"}},"type":"object","required":["data"],"title":"TemplateListResponse"},"RunOutput":{"properties":{"name":{"type":"string","title":"Name"},"mime_type":{"type":"string","title":"Mime Type"},"size_bytes":{"type":"integer","title":"Size Bytes"},"download_url":{"type":"string","title":"Download Url"}},"type":"object","required":["name","mime_type","size_bytes","download_url"],"title":"RunOutput"},"RunStatus":{"type":"string","enum":["running","succeeded","failed"],"title":"RunStatus","description":"The Run record's closed `status` vocabulary -- every writer\n(`run_admission.admit_run`, this module's own `reap_record`,\n`run_service.execute_run`) and every reader (the Public API's\n`RunResponse`, `router.py`'s own status branches) uses these members,\nnever the bare string literals a typo in one call site could drift\nfrom the others."},"RunResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"$ref":"#/components/schemas/RunStatus"},"template_id":{"type":"string","title":"Template Id"},"created_at":{"type":"string","format":"date-time","title":"Created At","example":"2024-01-01T00:00:00Z"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"date-time","title":"Completed At","example":"2024-01-01T00:00:00Z"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"date-time","title":"Expires At","example":"2024-01-01T00:00:00Z"},"rows_written":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Written"},"truncated":{"type":"boolean","title":"Truncated","default":false},"output":{"anyOf":[{"$ref":"#/components/schemas/RunOutput"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/RunErrorBody"},{"type":"null"}]}},"type":"object","required":["id","status","template_id","created_at"],"title":"RunResponse","description":"A Run, allowlisted: never the input filename, never a row or column\nvalue. `output`/`error` are `None` until the Run has one to report."},"app__domains__public_api__schemas__TemplateListItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"output_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Format"}},"type":"object","required":["id"],"title":"TemplateListItem"},"ErrorBody":{"description":"The one shape every `/api/v1` error answers with -- see\n`app.domains.public_api.errors`.","properties":{"error":{"title":"Error","type":"string"},"message":{"title":"Message","type":"string"}},"required":["error","message"],"title":"ErrorBody","type":"object"},"QuotaErrorBody":{"$defs":{"QuotaInfo":{"properties":{"used":{"title":"Used","type":"integer"},"limit":{"title":"Limit","type":"integer"},"resets_at":{"example":"2024-01-01T00:00:00Z","format":"date-time","title":"Resets At","type":"string"}},"required":["used","limit","resets_at"],"title":"QuotaInfo","type":"object"}},"description":"`ErrorBody` plus the `quota` key `EXPORT_LIMIT_REACHED` alone carries.","properties":{"error":{"title":"Error","type":"string"},"message":{"title":"Message","type":"string"},"quota":{"$ref":"#/$defs/QuotaInfo"}},"required":["error","message","quota"],"title":"QuotaErrorBody","type":"object"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}}}}