{"openapi":"3.1.0","info":{"title":"Spin Studio API","version":"1.0.0","description":"Render vehicle photographs into a studio.\n\nAuthenticate with `Authorization: Bearer sk_live_...`. A key sent in the\nquery string is refused: by the time it reaches this server it is already\nin the access log, so it has to be treated as compromised.\n\nEvery reply carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and\n`X-RateLimit-Reset`. A 429 carries `Retry-After` in seconds. Reads and\nwrites are limited separately.\n\nA POST may carry `Idempotency-Key`; replaying it returns the first\nanswer instead of starting a second render."},"servers":[{"url":"https://app.autorestage.com/api/v1"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_..."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}},"security":[{"bearerAuth":[]}],"paths":{"/account":{"get":{"summary":"What this key is","responses":{"200":{"description":"The key and the account it belongs to."},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/images/validate":{"post":{"summary":"Check photographs before paying to render them","description":"Scope: `renders:write`. Free. Answers the five checks this deployment has a model for -- exterior or interior shot, body type, angle, how far the car is tilted, and whether it is cut off by the frame -- plus `subject_coverage`, how much of the frame the segmenter isolated. Every check it does NOT run is listed in `unavailable`, so you can tell \"checked and fine\" from \"never looked\". `car_present` is one of those: there is no car detector here, and coverage is not a substitute (an empty showroom segments at 0.84). At most 20 images per call; counts against the write rate limit, because each one is a studio call.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["images"],"properties":{"images":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}}}}}}},"responses":{"200":{"description":"One result per image, in the order given."},"400":{"description":"The request is malformed, or a key was sent in the query string.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/merchandise/process":{"post":{"summary":"Process a vehicle by VIN, stock number or registration","description":"Scope: `renders:write`. Spyne's own merchandise contract, so an integration written against `api/pv1/merchandise` moves over without renaming fields. Give any ONE of `vin`, `stockNumber` or `registrationNumber`; the reply carries a `dealerVinID` you can look the vehicle up by later, or use the same identifier again. Answers 202 when everything was taken and **206 when only part of it was** -- read `data.rejected` before treating a 206 as a success. Images cost one credit each and a spin one per frame. `media.mediaKit`, `media.featureVideo` and `processingDetails.image.catalogCount` are refused with a 400 that names them: this deployment does not make them, and answering 202 for work that will never appear would be worse.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["media","mediaInput","processingDetails"],"properties":{"vin":{"type":"string","description":"17 characters, no I, O or Q."},"stockNumber":{"type":"string","maxLength":64},"registrationNumber":{"type":"string","maxLength":64},"clientMetaData":{"type":"object","description":"Yours; returned untouched on the lookup."},"media":{"type":"object","properties":{"image":{"type":"boolean"},"spin":{"type":"boolean"}}},"mediaInput":{"type":"object","properties":{"imageData":{"type":"array","maxItems":200,"items":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}},"description":"Duplicates are collapsed. At most 200."},"videoData":{"type":"array","maxItems":1,"items":{"type":"object","properties":{"url":{"type":"string","format":"uri"}}}}}},"processingDetails":{"type":"object","required":["backgroundId"],"properties":{"backgroundId":{"type":"string"},"spin":{"type":"object","properties":{"spinFrameCount":{"type":"string","enum":["36","72"]}}}}},"webhook_url":{"type":"string","format":"uri"}}}}}},"responses":{"202":{"description":"Everything asked for was started."},"206":{"description":"Part of the request was started; `data.rejected` says what was not and why."},"400":{"description":"The request is malformed, or a key was sent in the query string.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"402":{"description":"Not enough credit.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/merchandise":{"get":{"summary":"One vehicle and everything made for it","description":"Scope: `renders:read`. Find a vehicle by `dealerVinId`, `vin`, `stockNumber` or `registrationNumber`. A vehicle belonging to another dealership answers 404, the same as one that does not exist.","parameters":[{"name":"dealerVinId","in":"query","schema":{"type":"string"}},{"name":"vin","in":"query","schema":{"type":"string"}},{"name":"stockNumber","in":"query","schema":{"type":"string"}},{"name":"registrationNumber","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The vehicle and its media."},"400":{"description":"The request is malformed, or a key was sent in the query string.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"404":{"description":"No such render.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/credit":{"get":{"summary":"Balance, prices and what was spent","description":"The balance, the price of every product, and this dealership's own ledger lines -- never another tenant's. `limit` defaults to 50 and is capped at 500.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":50}}],"responses":{"200":{"description":"Balance, prices and ledger lines."},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/backgrounds":{"get":{"summary":"The studios this key may render onto","description":"Scope: `backgrounds:read`.","responses":{"200":{"description":"Every background, with a thumbnail."},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/renders":{"post":{"summary":"Start a render","description":"Scope: `renders:write`. Answers 202 and renders in the background; poll `/renders/{id}` or give a `webhook_url`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Replaying the same value returns the first answer."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["background_id","images"],"properties":{"background_id":{"type":"string","description":"From GET /backgrounds."},"images":{"type":"array","maxItems":50,"items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"A public https URL. Private addresses are refused."}}}},"name":{"type":"string","maxLength":120},"webhook_url":{"type":"string","format":"uri","description":"Told once, when the render finishes."}}}}}},"responses":{"202":{"description":"Accepted; the render is running."},"400":{"description":"The request is malformed, or a key was sent in the query string.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"402":{"description":"Not enough credit.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"413":{"description":"The body is too large.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/spins":{"post":{"summary":"Turn a walk-around video into a 360 spin","description":"Scope: `renders:write`. This server fetches the video from `video_url`, so it must be a public https URL. Up to 500MB. Answers 202; poll `/spins/{id}` or give a `webhook_url`. Costs one credit per frame -- a 36-frame spin is 36 -- taken before the video is fetched and given straight back if the spin cannot be made. A 402 carries `needed` and `balance`; `GET /account` has the balance and the price of every product.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["video_url","background_id"],"properties":{"video_url":{"type":"string","format":"uri"},"background_id":{"type":"string"},"name":{"type":"string","maxLength":120},"frames":{"type":"integer","minimum":12,"maximum":72,"description":"How many positions the spin turns through. 36 by default."},"webhook_url":{"type":"string","format":"uri"}}}}}},"responses":{"202":{"description":"Accepted; the spin is rendering."},"400":{"description":"The request is malformed, or a key was sent in the query string.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"402":{"description":"Not enough credit.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"413":{"description":"The body is too large.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/spins/{id}":{"get":{"summary":"One spin","description":"Scope: `renders:read`. When it has succeeded the reply carries every frame, a viewer URL you can put in an iframe, and the walk-around video tour if one was built.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Status, frames and the viewer URL."},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"404":{"description":"No such render.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}},"/renders/{id}":{"get":{"summary":"One render","description":"Scope: `renders:read`. A render belonging to another account is 404, never 403.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Status and the finished images."},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"403":{"description":"The key does not have the scope this endpoint needs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"404":{"description":"No such render.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}},"429":{"description":"Rate limited. See Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}}}}},"x-webhooks":{"spin.succeeded":{"post":{"summary":"Sent when a spin finishes","description":"Same signature scheme as render.succeeded."}},"render.succeeded":{"post":{"summary":"Sent when a render finishes","description":"Signed: `X-Spin-Signature: t=<unix>,v1=<hex>` where v1 is HMAC-SHA256 of `${t}.${rawBody}` with your webhook secret. Verify locally; reject anything more than 5 minutes old. The timestamp is inside the signed material, so it cannot be moved."}}}}