{"openapi":"3.1.0","info":{"title":"Image Enhancer API","version":"1.0.0","description":"Token-authenticated asynchronous image enhancement. Submit an image, poll the task, then download the result when completed."},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Access token created in the /admin dashboard. Also accepted as X-API-Key."},"adminAuth":{"type":"http","scheme":"bearer","description":"Operator admin token configured by the service operator. Separate from enh_ access tokens."},"adminToken":{"type":"apiKey","in":"header","name":"X-Admin-Token","description":"Alternative to Authorization: Bearer for the admin API."}},"schemas":{"Job":{"type":"object","required":["job_id","status","status_url","result_url","created_at","started_at","completed_at","attempts"],"properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"status_url":{"type":"string"},"result_url":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"attempts":{"type":"integer"},"queue_position":{"type":"integer","description":"Present while queued; 1 means next."},"error":{"type":"string"},"processing_ms":{"type":"number"},"inference_ms":{"type":"number"},"queue_ms":{"type":"number"}}},"Error":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}},"AdminKey":{"type":"object","required":["id","name","prefix","created_at","rate_limit_per_minute","daily_quota","lifetime_quota"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"First twelve characters of the plaintext token."},"created_at":{"type":"number","description":"Unix seconds."},"last_used_at":{"type":["number","null"],"description":"Unix seconds."},"revoked_at":{"type":["number","null"],"description":"Unix seconds."},"rate_limit_per_minute":{"type":"integer"},"daily_quota":{"type":"integer","description":"Daily quota; 0 means unlimited."},"lifetime_quota":{"type":"integer","description":"Lifetime quota; 0 means unlimited. Once exhausted, the token may still create at most 3 tasks per UTC day and 1 per UTC hour."},"requests_total":{"type":"integer"},"jobs_total":{"type":"integer","description":"Lifetime tasks submitted with this token."},"jobs_today":{"type":"integer"},"rate":{"type":"object","properties":{"limit":{"type":"integer"},"used":{"type":"integer"},"remaining":{"type":["integer","null"]},"reset_at":{"type":["number","null"],"description":"Unix seconds."}}}}},"AdminKeyWithToken":{"allOf":[{"$ref":"#/components/schemas/AdminKey"},{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"Plaintext token; returned only once."}}}]},"AdminKeyInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80},"rate_limit_per_minute":{"type":"integer","minimum":1,"maximum":600,"default":6},"daily_quota":{"type":"integer","minimum":0,"maximum":10000,"default":20,"description":"0 means unlimited."},"lifetime_quota":{"type":"integer","minimum":0,"maximum":1000000,"default":1000,"description":"0 means unlimited."}}},"AdminKeyPatch":{"type":"object","description":"At least one field must be provided.","properties":{"name":{"type":"string","maxLength":80},"rate_limit_per_minute":{"type":"integer","minimum":1,"maximum":600},"daily_quota":{"type":"integer","minimum":0,"maximum":10000},"lifetime_quota":{"type":"integer","minimum":0,"maximum":1000000}}},"AdminDefaults":{"type":"object","required":["rate_limit_per_minute","daily_quota","lifetime_quota"],"description":"Defaults applied to newly created access tokens. Existing tokens keep their stored limits.","properties":{"rate_limit_per_minute":{"type":"integer","minimum":1,"maximum":600,"description":"Default per-minute request limit for new tokens."},"daily_quota":{"type":"integer","minimum":0,"maximum":10000,"description":"Default daily quota for new tokens; 0 means unlimited."},"lifetime_quota":{"type":"integer","minimum":0,"maximum":1000000,"description":"Default lifetime quota for new tokens; 0 means unlimited."}}},"AdminDefaultsPatch":{"type":"object","description":"At least one field must be provided. Only affects tokens created afterwards.","properties":{"rate_limit_per_minute":{"type":"integer","minimum":1,"maximum":600},"daily_quota":{"type":"integer","minimum":0,"maximum":10000},"lifetime_quota":{"type":"integer","minimum":0,"maximum":1000000}}},"JobCounts":{"type":"object","properties":{"queued":{"type":"integer"},"processing":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"cancelled":{"type":"integer"}}},"AdminWorker":{"type":"object","required":["id","status","started_at","heartbeat_at"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["loading","ready","stopping","offline"]},"started_at":{"type":"number","description":"Unix seconds."},"heartbeat_at":{"type":"number","description":"Unix seconds."},"current_job_id":{"type":["string","null"]},"stale":{"type":"boolean"}}},"AdminOverview":{"type":"object","required":["status","queue_depth","counts","today","workers","keys","generated_at"],"properties":{"status":{"type":"string","enum":["ready","degraded"]},"queue_depth":{"type":"integer"},"counts":{"$ref":"#/components/schemas/JobCounts"},"today":{"$ref":"#/components/schemas/JobCounts"},"workers":{"type":"array","items":{"$ref":"#/components/schemas/AdminWorker"}},"keys":{"type":"array","items":{"$ref":"#/components/schemas/AdminKey"}},"storage_free_bytes":{"type":["number","null"]},"generated_at":{"type":"number","description":"Unix seconds."}}},"AdminJob":{"type":"object","required":["id","status","created_at","prompt","output_format","seed","attempts"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"created_at":{"type":"number","description":"Unix seconds."},"updated_at":{"type":"number","description":"Unix seconds."},"started_at":{"type":["number","null"],"description":"Unix seconds."},"completed_at":{"type":["number","null"],"description":"Unix seconds."},"prompt":{"type":"string"},"output_format":{"type":"string","enum":["png","jpeg"]},"seed":{"type":"string","pattern":"^[0-9]+$","description":"64-bit integer represented as a decimal string."},"media_type":{"type":["string","null"]},"processing_ms":{"type":["number","null"]},"inference_ms":{"type":["number","null"]},"queue_ms":{"type":["number","null"]},"attempts":{"type":"integer"},"error":{"type":["string","null"]},"api_key_id":{"type":["string","null"]},"api_key_name":{"type":["string","null"]},"api_key_prefix":{"type":["string","null"]}}},"AdminJobList":{"type":"object","required":["jobs","total","limit","offset"],"properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/AdminJob"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}},"security":[{"bearerAuth":[]}],"paths":{"/v1/enhance":{"post":{"summary":"Create an enhancement task","description":"Accepts exactly one image as multipart/form-data. Returns 202 and a task object.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Scoped to the access token. Repeating the same request returns the existing task; a different request returns 409.","schema":{"type":"string","minLength":1,"maxLength":128}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary","description":"JPEG, PNG, or WebP; up to 32 MiB, 16384 px per edge, 16 megapixels."},"prompt":{"type":"string","maxLength":2000,"description":"Additional guidance appended to the built-in prompt."},"output_format":{"type":"string","enum":["png","jpeg"],"default":"png"},"seed":{"type":"string","pattern":"^[0-9]+$","description":"0 to 2^63-1; passed through as an integer."}}}}}},"responses":{"200":{"description":"Existing task returned for an idempotent replay","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"202":{"description":"Task queued","headers":{"Location":{"description":"Relative task URL.","schema":{"type":"string"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}},"X-Quota-Limit":{"schema":{"type":"integer"}},"X-Quota-Remaining":{"schema":{"type":"integer"}},"X-Quota-Reset":{"schema":{"type":"integer"}},"X-Lifetime-Quota-Limit":{"schema":{"type":"integer"}},"X-Lifetime-Quota-Remaining":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"401":{"description":"Access token missing, invalid, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency-Key was used for a different request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Upload or image dimensions too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Unsupported image format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid or missing form fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit, daily quota, lifetime quota (including the fixed 3/day, 1/hour overflow lane), or queue capacity exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Service not ready or temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/jobs/{job_id}":{"get":{"summary":"Read task status","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Task status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"401":{"description":"Access token missing, invalid, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Task not found or not owned by this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Cancel a queued task","description":"A processing task cannot be cancelled. Completed, failed, or cancelled tasks are returned unchanged.","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Task status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"401":{"description":"Access token missing, invalid, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Task not found or not owned by this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Task is processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/jobs/{job_id}/result":{"get":{"summary":"Download a completed result","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Enhanced image","headers":{"Content-Disposition":{"schema":{"type":"string"}},"X-Enhancer-Seed":{"schema":{"type":"string"}},"X-Enhancer-Processing-Ms":{"schema":{"type":"number"}},"X-Enhancer-Inference-Ms":{"schema":{"type":"number"}},"X-Enhancer-Queue-Ms":{"schema":{"type":"number"}}},"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Access token missing, invalid, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Task not found or not owned by this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Task is not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Result file expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/overview":{"get":{"tags":["Admin"],"summary":"Read the operator dashboard overview","description":"Requires the operator admin token, not an enh_ access token. Returns service status, task counts, token usage, and capacity.","security":[{"adminAuth":[]},{"adminToken":[]}],"responses":{"200":{"description":"Dashboard overview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOverview"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/jobs":{"get":{"tags":["Admin"],"summary":"List jobs for operators","description":"Requires the operator admin token. Searches task IDs and prompts.","security":[{"adminAuth":[]},{"adminToken":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]}},{"name":"key_id","in":"query","description":"Filter by the access token that created the job.","schema":{"type":"string"}},{"name":"search","in":"query","description":"Matches task IDs and prompts.","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Job page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminJobList"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/keys":{"post":{"tags":["Admin"],"summary":"Create an access token","description":"Requires the operator admin token. Omitted limits come from the admin-managed defaults. The plaintext token is returned only in this response; only its SHA-256 hash is stored.","security":[{"adminAuth":[]},{"adminToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminKeyInput"}}}},"responses":{"201":{"description":"Access token created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminKeyWithToken"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid name, rate limit, or quota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/keys/{id}":{"patch":{"tags":["Admin"],"summary":"Update an access token","description":"Requires the operator admin token. At least one of name, rate_limit_per_minute, daily_quota, or lifetime_quota must be provided.","security":[{"adminAuth":[]},{"adminToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminKeyPatch"}}}},"responses":{"200":{"description":"Updated access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminKey"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Access token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No changes or invalid fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Admin"],"summary":"Revoke an access token","description":"Requires the operator admin token. Revocation takes effect immediately. Revoking an already revoked token returns its current state.","security":[{"adminAuth":[]},{"adminToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminKey"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Access token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/settings":{"get":{"tags":["Admin"],"summary":"Read defaults for new access tokens","description":"Requires the operator admin token. These defaults are applied when a token is created without explicit limits.","security":[{"adminAuth":[]},{"adminToken":[]}],"responses":{"200":{"description":"Default limits for new tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDefaults"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Admin"],"summary":"Update defaults for new access tokens","description":"Requires the operator admin token. Changes apply only to tokens created afterwards; existing tokens keep their stored limits.","security":[{"adminAuth":[]},{"adminToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDefaultsPatch"}}}},"responses":{"200":{"description":"Updated default limits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDefaults"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No changes or invalid values","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/api/jobs/{id}":{"delete":{"tags":["Admin"],"summary":"Cancel a queued job","description":"Requires the operator admin token. Only queued jobs can be cancelled; processing jobs return 409. Terminal jobs are returned unchanged.","security":[{"adminAuth":[]},{"adminToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job state after the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminJob"}}}},"401":{"description":"Admin token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Job is processing and cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many admin authentication failures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}