{"openapi":"3.1.0","info":{"title":"Zoovdev API","version":"1.0.0","description":"Automation for Max plans. Authenticate with `Authorization: Bearer zv_…` (keys at https://zoov.dev/app/api). JSON in, JSON out. Files never go to this host: a file call answers with an upload URL on the conversion box, and that URL answers with the result."},"servers":[{"url":"https://zoov.dev/api/public/v1"}],"security":[{"key":[]}],"components":{"securitySchemes":{"key":{"type":"http","scheme":"bearer","bearerFormat":"zv_ key"}},"schemas":{"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"One line for a person."},"code":{"type":"string","description":"Stable; branch on this, never on the message."}},"additionalProperties":true},"Run":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["waiting","running","done","kept","failed","expired"]},"name":{"type":["string","null"]},"kind":{"type":"string","enum":["ydr","ydd","yft","ytd"]},"bytesIn":{"type":"integer"},"bytesOut":{"type":["integer","null"]},"outcome":{"type":["string","null"],"enum":["smaller","larger","same",null]},"code":{"type":["string","null"],"description":"Why a file was kept (nothing_to_do, not_smaller, would_drop_textures) or failed."},"ms":{"type":["integer","null"]},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"}}},"OptimizeSettings":{"type":"object","description":"Optional. Defaults shrink a file without touching a vertex: textures capped at 1024 px, formats only ever made lighter, mipmaps rebuilt, the original kept when nothing gets smaller.","properties":{"textures":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"maxSize":{"type":"integer","enum":[256,512,1024,2048,4096],"default":1024},"forceSize":{"type":"boolean","default":false,"description":"Resize every texture to maxSize, not only larger ones."},"format":{"type":"string","enum":["auto","dxt5","dxt1","bc7"],"default":"auto"},"mips":{"type":"boolean","default":true}}},"meshes":{"type":"object","description":"YDR, YDD and YFT only.","properties":{"enabled":{"type":"boolean","default":false},"detail":{"type":"number","minimum":0.4,"maximum":1,"default":1,"description":"Share of triangles kept."},"lods":{"type":"boolean","default":true,"description":"Add missing LODs."}}},"keepSmaller":{"type":"boolean","default":true,"description":"Answer with the original when the new file is not smaller."}}}}},"paths":{"/me":{"get":{"summary":"The key: account, plan, scopes, credits, Optimize allowance","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]}}},"plan":{"type":"string"},"key":{"type":"object","properties":{"id":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"requestsPerMinute":{"type":"integer"}}},"credits":{"type":"object","properties":{"balance":{"type":"integer"},"permanent":{"type":"integer"},"temporary":{"type":"integer"}}},"optimize":{"type":"object","properties":{"perDay":{"type":["integer","null"]},"used":{"type":"integer"},"active":{"type":"integer"},"concurrent":{"type":"integer","description":"Uploads to keep in flight at most."},"maxFileBytes":{"type":"integer"},"resetAt":{"type":["string","null"]}}}}}}}},"401":{"description":"unauthorized: missing, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_required (Max only), scope_missing, account_suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited (per key per minute) or quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/optimize":{"post":{"summary":"Start an optimize: answers where to send the file","description":"Step 1 of 2. Then POST multipart/form-data to `upload.url` with `file` (named like `name`) and optional `settings` (JSON, see OptimizeSettings). That answer is either the optimized file (`application/octet-stream`, result JSON in the `X-Optimize-Result` header) or JSON `{ result }` with `result.kept` set when the original is the better file. Failures there are `{ detail: { code, error, retryable } }`. The URL is good for one upload until `run.expiresAt`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","bytes"],"properties":{"name":{"type":"string","example":"prop_crate.ydr","description":"Ends in .ydr, .ydd, .yft, .ytd"},"bytes":{"type":"integer","description":"File size; checked against the plan before upload."},"kind":{"type":"string","enum":["ydr","ydd","yft","ytd"],"description":"Only when the name has no extension."}}}}}},"responses":{"201":{"description":"Run opened","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string"},"state":{"const":"waiting"},"name":{"type":"string"},"kind":{"type":"string"},"maxBytes":{"type":"integer"},"expiresAt":{"type":"string","format":"date-time"}}},"upload":{"type":"object","properties":{"method":{"const":"POST"},"url":{"type":"string","format":"uri"},"contentType":{"const":"multipart/form-data"},"fields":{"type":"object"}}}}}}}},"400":{"description":"bad_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_required (Max only), scope_missing, account_suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"concurrency_limit (wait for a running file), in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"file_too_large (maxBytes in the body)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited (per key per minute) or quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/optimize/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"A run: waiting, running, done, kept, failed or expired","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"$ref":"#/components/schemas/Run"}}}}}},"401":{"description":"unauthorized: missing, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_required (Max only), scope_missing, account_suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited (per key per minute) or quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Give back a run whose file will not be sent (frees a concurrent slot)","responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"$ref":"#/components/schemas/Run"}}}}}},"401":{"description":"unauthorized: missing, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_required (Max only), scope_missing, account_suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"concurrency_limit (wait for a running file), in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited (per key per minute) or quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}