{"openapi":"3.1.0","info":{"title":"Solorva API","version":"1.0.0","description":"Solorva learns a brand once, then writes and designs on it.\n\nGeneration takes 45–90 seconds, so it is modelled as a job: `POST /v1/generations`\nanswers immediately with an id, and `GET /v1/generations/{id}` reads it back. Add\n`?wait=90` to either call to have the server hold the response open until the job\nfinishes — you should never need to write a polling loop.\n\nAuthenticate with `Authorization: Bearer sv_live_…`. Keys are created in the\nSolorva dashboard under Settings → API keys, are shown once, and carry scopes.\n\n**Retrying safely.** Every `POST` accepts an optional `Idempotency-Key` header. Send a\nfresh UUID per logical request and reuse it on retries: a repeat within 24 hours gets the\nfirst response back — the same job id — and is not charged again. See the header's\ndescription on any POST for the exact rules."},"servers":[{"url":"https://api.solorva.com/api","description":"Solorva API"}],"security":[{"apiKey":[]}],"tags":[{"name":"Generations","description":"Create content and research."},{"name":"Brands","description":"The brands on the account: add one from a website, read its profile, its assets and its branding."},{"name":"Images","description":"Adapt a generated image for another channel."},{"name":"Decks","description":"Presentations: written, rendered and exported as PDF."},{"name":"Account","description":"Credits and key information."}],"paths":{"/v1":{"get":{"tags":["Account"],"operationId":"getApiInfo","summary":"What this key can do","description":"Returns the key's name, scopes, and the endpoints available. Requires no scope.","responses":{"200":{"description":"Key and endpoint information."},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/brands":{"get":{"tags":["Brands"],"operationId":"listBrands","summary":"List brands","description":"The brands on this account. Use a brand `id` as `brand_id` when starting a generation. `ready` is false for a brand whose website has not been analysed yet — generating against one produces generic copy rather than on-brand work.","security":[{"apiKey":["brands:read"]}],"responses":{"200":{"description":"A list of brands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandList"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Brands"],"operationId":"createBrand","summary":"Add a brand from its website","description":"Learn a new brand from its website — voice, tone, palette, typography and positioning —\nexactly as adding a brand in the dashboard does. Takes most of a minute, so it is a job:\nanswers `202` at once with an import id, or `200` with the finished import if `wait` is\ngiven and it finishes in time. Read it back with `GET /v1/brand-imports/{id}`, or\nsubscribe to the `brand_import.completed` webhook.\n\n**Costs no credits**, as in the dashboard, but needs an active plan and counts toward the\naccount's brand limit — analyses still running count too. At most two run at once.\n\nThe site is fetched by the same guarded fetcher as the dashboard's: an address on a\nprivate network, localhost or a cloud metadata endpoint is refused and the import fails.\nThe page's text is treated as data, never as instructions.","security":[{"apiKey":["brands:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBrand"}}}},"responses":{"200":{"description":"The import finished within `wait` — done, or failed with a reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandImport"}}}},"202":{"description":"Accepted and running. Read it back with GET /v1/brand-imports/{id}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandImport"}}}},"400":{"description":"url is missing or is not an http(s) address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no active plan, or has reached its spending limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The brand limit is reached (`brand_limit_reached`), or the key lacks brands:write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited, or two imports are already running on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/v1/brands/{brand_id}/profile":{"get":{"tags":["Brands"],"operationId":"getBrandProfile","summary":"Read a brand's profile (brand DNA)","description":"What Solorva learned about a brand: its voice and tone, positioning and audience, palette,\ntypography and visual style, and the content rules a writer should follow. Free.\n\n**A stable, versioned shape.** Every field is always present with a fixed type — strings\nare `\"\"` when unknown, lists `[]`, colours and fonts `null` — so test for emptiness, never\nfor a missing key. `profile_version` changes only if a field is removed, renamed or\nretyped; new fields may appear without it changing.\n\n`ready` is false for a brand whose website has not been analysed; its fields are empty.","security":[{"apiKey":["brands:read"]}],"parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string"},"description":"From `GET /v1/brands`."}],"responses":{"200":{"description":"The brand profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandProfile"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/brand-imports/{id}":{"get":{"tags":["Brands"],"operationId":"getBrandImport","summary":"Read a brand import","description":"Reads an import started by `POST /v1/brands`. Add `?wait=90` to hold the response open until it finishes. When `status` is `done`, `brand_id` is the new brand. Free.","security":[{"apiKey":["brands:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The import id returned by POST /v1/brands."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":120,"default":0},"description":"Seconds to wait for the import to finish before answering. 0 answers immediately."}],"responses":{"200":{"description":"The import, in whatever state it is in.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandImport"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand import with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/credits":{"get":{"tags":["Account"],"operationId":"getCredits","summary":"Remaining credits","description":"One credit buys one item. A generation asking for `num_items: 5` costs five content credits, and research costs one credit per report. `allowance` resets at the end of the billing period; `purchased` credits do not expire.","security":[{"apiKey":["credits:read"]}],"responses":{"200":{"description":"Credit balances.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/generations":{"post":{"tags":["Generations"],"operationId":"createGeneration","summary":"Start a generation","description":"Starts work and returns a job. Costs one content credit per item produced — so `num_items: 5` costs five — or one research credit per report. Charged only if the job succeeds, and only for what it actually delivered.\n\nWithout `wait`, answers `202` immediately with an id and `status: \"running\"`.\nWith `wait`, holds the response open for up to that many seconds and answers `200`\nwith the finished results if the job completes in time — or `202` with the id if it\ndoes not, in which case the job is still running and nothing has been lost.\n\nMost generations finish inside 90 seconds; research can take longer.","security":[{"apiKey":["generations:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGeneration"}}}},"responses":{"200":{"description":"The generation finished within `wait`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generation"}}}},"202":{"description":"Accepted and running. Read it back with GET /v1/generations/{id}.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generation"}}}},"400":{"description":"The request body was not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has reached its spending limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"No credits remaining, email not confirmed, or the key lacks the generations:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited, or too many generations already running on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]},"get":{"tags":["Generations"],"operationId":"listGenerations","summary":"List generations","description":"This account's generations, newest first. Free.\n\nThis is how a generation is recovered when the call that started it was abandoned\nbefore its id came back — the work carried on and is here even though the answer\nnever arrived.\n\nEach item has the same shape as `GET /v1/generations/{id}` **without `results`**; read\none by id for its content. Paginate with `starting_after` set to `next_cursor` from\nthe previous page. Generations on a deleted brand are not listed.","security":[{"apiKey":["generations:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Items per page."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"A generation id: return the ones older than it. Use `next_cursor` from the previous page."},{"name":"brand_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only generations for this brand."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["running","done","error","cancelled"]},"description":"Only generations in this state."}],"responses":{"200":{"description":"A page of generations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerationList"}}}},"400":{"description":"A query parameter was not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"brand_id names no brand on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/images/presets":{"get":{"tags":["Images"],"operationId":"listImagePresets","summary":"List resize presets","description":"The named sizes accepted by `POST /v1/images/resize`, so you do not have to guess numbers.","security":[{"apiKey":["brands:read"]}],"responses":{"200":{"description":"Named sizes with their dimensions."},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/images/resize":{"post":{"tags":["Images"],"operationId":"resizeImage","summary":"Resize a generated image","description":"Re-crop an image Solorva already generated to another channel's shape — an Instagram\nstory from a square post, a YouTube thumbnail from a landscape ad.\n\n**Free.** No credit is spent and no model is called: the crop is done locally and\nreturns in about a second. Use it as often as you like — one generation can become\nevery size a campaign needs.\n\nIt picks the most interesting region to keep rather than blindly cropping the centre,\nso an off-centre subject survives a wide-to-tall reframe. The original image and the\ncontent it belongs to are left untouched.","security":[{"apiKey":["generations:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResizeImage"}}}},"responses":{"200":{"description":"The resized image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DerivedImage"}}}},"400":{"description":"The content has no images, the index is out of range, or the size is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No content with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/v1/brands/{brand_id}/assets":{"post":{"tags":["Brands"],"operationId":"addBrandAssets","summary":"Add files to a brand's library","description":"Upload photos, graphics, logo variations or product shots for this brand. Up to\nten per request, 5MB each.\n\n**Free.** Storage is billed by the byte, but metering an upload would charge\nfractions of a cent for the thing that makes paid generations better. Ceilings do\nthe work instead: 100 assets and 250MB per brand.\n\n**The whole request is refused rather than part-filled.** A caller that has to\nwork out which three of its five files landed will get it wrong.\n\nFiles are checked against the format's magic number rather than the declared\ncontent type, so an HTML file named `logo.png` is rejected.","security":[{"apiKey":["brands:write"]}],"parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string"},"description":"From `GET /v1/brands`."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandAssetsWrite"}}}},"responses":{"201":{"description":"The assets that were added.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandAssetList"}}}},"400":{"description":"A file was not an accepted image, or a ceiling would have been exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"File storage is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Brands"],"operationId":"listBrandAssets","summary":"List a brand's uploaded assets","description":"The photos, graphics, logo variations and product shots the customer has given\nthis brand — and which few of them are attached to every generation.\n\n**Free**, and worth reading before generating for an unfamiliar brand: what is in\nthe library shapes what the output can look like.\n\n`used_on_every_generation` is the field that matters. A library of forty\nphotographs and the small set attached to each request are different things, and\na caller that conflates them will report that a logo is being used when it is not.\nAssets are uploaded and marked in the Solorva dashboard; this endpoint reads.","security":[{"apiKey":["brands:read"]}],"parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string"},"description":"From `GET /v1/brands`."}],"responses":{"200":{"description":"The brand's assets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandAssetList"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/brands/{brand_id}/branding":{"patch":{"tags":["Brands"],"operationId":"setBrandBranding","summary":"Set a brand's header and footer","description":"Change what is stamped onto every graphic generated for this brand.\n\n**Free**, and it takes effect on the next generation. **Existing work is never\nchanged.**\n\n**A partial update.** Only the slots named in the request are touched, so a call\nthat changes a footer line cannot accidentally clear a header it never mentioned.\nEmptying a slot is `{\"type\": \"none\"}` — explicit rather than inferred from an\nomission, because \"leave this alone\" and \"remove this\" must not be the same\nrequest.\n\nAn `asset` slot must name an asset already in *this* brand's library. One that\nis not is refused: an id from another account would otherwise be composited onto\nthese graphics.","security":[{"apiKey":["brands:write"]}],"parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string"},"description":"From `GET /v1/brands`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandBrandingWrite"}}}},"responses":{"200":{"description":"The stored configuration, read back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandBranding"}}}},"400":{"description":"A slot named an asset that is not in this brand's library, or a colour was not a hex value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Brands"],"operationId":"getBrandBranding","summary":"Read a brand's header and footer","description":"What Solorva stamps onto every graphic generated for this brand — a logo, a\ncopyright line, contact details — and whether it is switched on.\n\n**Free.** This content is composited *after* the image is generated rather than\ndrawn by the image model, so the text is exact.\n\n**Do not ask a generation to include a footer.** If one is configured it is added\nautomatically; asking for one as well produces a second, misspelled copy, because\nimage models render text approximately.\n\n`enabled` reports whether it is actually applied, which is not the same as whether\nit is configured — a filled-in configuration with the switch off draws nothing.","security":[{"apiKey":["brands:read"]}],"parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string"},"description":"From `GET /v1/brands`."}],"responses":{"200":{"description":"The brand's header and footer configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandBranding"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/decks":{"post":{"tags":["Decks"],"operationId":"createDeck","summary":"Write a presentation","description":"Produce a presentation on a brand — outline, copy, layout and typography — and return\nits slide text with a link to a PDF.\n\n**Costs 2 credits, plus 1 for each generated picture.** Pictures are off by default:\nsix image calls take about two minutes, and a text-only deck takes roughly fifteen\nseconds. Charged for what was produced, so a deck whose pictures all failed costs 2.\n\nEvery word on a slide is real text composited by Solorva rather than drawn into an\nimage, so it renders exactly as written and can be edited afterwards.\n\nThe deck is saved before this call answers. If your client gives up waiting, the work\nis not lost — find it with `GET /v1/decks`.","security":[{"apiKey":["generations:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["brand_id","topic"],"properties":{"brand_id":{"type":"string","description":"Which brand to write it for."},"topic":{"type":"string","minLength":3,"maxLength":2000,"description":"What the deck is about. A sentence or two of direction beats a bare title."},"slide_count":{"type":"integer","minimum":3,"maximum":20,"default":8},"with_images":{"type":"boolean","default":false,"description":"Generate pictures. Adds up to 6 credits and about two minutes."}}}}}},"responses":{"201":{"description":"The deck.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deck"}}}},"400":{"description":"The request body was not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not enough credits, or the key lacks generations:write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No brand with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]},"get":{"tags":["Decks"],"operationId":"listDecks","summary":"List decks","description":"The decks on this account, newest first. Free. Also how a deck is recovered when a create call was abandoned before the id came back.","security":[{"apiKey":["generations:read"]}],"parameters":[{"name":"brand_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only decks for this brand."}],"responses":{"200":{"description":"A list of decks.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Deck"}}}}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/decks/{id}":{"get":{"tags":["Decks"],"operationId":"getDeck","summary":"Read a deck","description":"A deck by id, with its slide text and a fresh PDF link. Free. The PDF is rendered on demand and the link is signed; reading the deck again mints a new one.","security":[{"apiKey":["generations:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The deck.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deck"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No deck with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/images/variants":{"post":{"tags":["Images"],"operationId":"generateImageVariants","summary":"Create variants of a generated image","description":"Produce alternative versions of an image Solorva already generated — the same brand,\nsubject and palette, recomposed. For choosing between options, or filling a campaign\nfrom one approved design.\n\n**Costs one content credit per variant**, not per call, and takes roughly 30-60\nseconds. This is the only endpoint whose price depends on a parameter, so `count` is\nworth reading twice. The whole batch is checked against your balance before any work\nstarts, and asking for more than you can afford is refused rather than part-filled.\n\nEvery returned image is compared against its source before it is charged for. A model\nthat hands back the original — or the same picture twice in one batch — has produced\nnothing, and those are discarded, listed in `rejected`, and **not charged**. So\n`credits_charged` can be lower than `count`, and `data` can be shorter than you asked\nfor. If nothing usable comes back at all, the call fails and nothing is charged.\n\nThe original image and the content it belongs to are left untouched.","security":[{"apiKey":["generations:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageVariants"}}}},"responses":{"200":{"description":"The variants that passed the difference check, and what was discarded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageVariantList"}}}},"400":{"description":"The content has no images, the index is out of range, or count is outside 1-4.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has reached its spending limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not enough credits for the number of variants asked for, or the key lacks the generations:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No content with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"No usable variants were produced. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/v1/images/remove-background":{"post":{"tags":["Images"],"operationId":"removeImageBackground","summary":"Remove an image background","description":"Cut the subject out of a generated image, returning a PNG with a genuinely transparent\nbackground — for placing a product on a coloured layout, another photo, or merchandise.\n\n**Costs one content credit** and takes roughly 20-40 seconds.\n\nThe result is verified to be actually transparent before it is stored or charged for.\nIf the model returns a flat image the call fails with `no_transparency` and **nothing is\ncharged** — so a result you receive is always a real cut-out rather than a background\npainted white.","security":[{"apiKey":["generations:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveBackground"}}}},"responses":{"200":{"description":"The cut-out image, as a transparent PNG.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DerivedImage"}}}},"400":{"description":"The content has no images, or the index is out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has reached its spending limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"No credits remaining, or the key lacks the generations:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No content with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`: a request with this Idempotency-Key is still being answered. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused`: this Idempotency-Key was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The model could not produce a transparent result. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/v1/generations/{id}":{"get":{"tags":["Generations"],"operationId":"getGeneration","summary":"Read a generation","description":"Reads a job. Add `?wait=90` to hold the response open until it reaches a final state. A wait that runs out is not an error — the job is returned still running, and asking again with the same URL continues waiting.","security":[{"apiKey":["generations:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The generation id returned when it was created."},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":120,"default":0},"description":"Seconds to wait for the job to finish before answering. 0 answers immediately."}],"responses":{"200":{"description":"The generation, in whatever state it is in.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Generation"}}}},"401":{"description":"Missing, invalid or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not have the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No generation with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Makes a retry safe. Optional; 1–255 visible ASCII characters — a UUID is ideal.\n\n- The first request with a key claims it before any work starts. A successful (2xx)\n  response is stored for **24 hours**.\n- A repeat with the same key and the same body returns that stored response — the same\n  job id — with `Idempotent-Replayed: true`, **does no work and charges nothing**.\n- A repeat while the first is still being answered gets `409 idempotency_in_progress`;\n  retry shortly.\n- The same key with a different body (or a different endpoint) gets\n  `422 idempotency_key_reused`.\n- A response that is not a success (400, 402, 403, 429, 5xx) is not stored: nothing was\n  started or charged, so the same key can be retried.\n\nKeys belong to the API key that sent them. Another key — including another key on the\nsame account — never sees them."}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A Solorva API key, e.g. `Authorization: Bearer sv_live_…`."}},"schemas":{"CreateGeneration":{"type":"object","required":["brand_id"],"properties":{"brand_id":{"type":"string","description":"Which brand to generate on. From GET /v1/brands."},"type":{"type":"string","enum":["content","research"],"default":"content","description":"`content` writes and designs marketing assets. `research` produces written reports about the market."},"wait":{"type":"integer","minimum":0,"maximum":120,"default":0,"description":"Seconds to wait for the result before answering. 0 returns a job id immediately."},"input":{"oneOf":[{"$ref":"#/components/schemas/ContentInput"},{"$ref":"#/components/schemas/ResearchInput"}],"description":"What to make. Fields depend on `type`; everything is optional and sensible defaults apply."}}},"ContentInput":{"type":"object","description":"Used when `type` is `content`. Every field is optional.","properties":{"content_type":{"type":"string","enum":["ad","post","poster","carousel","thumbnail","blog"],"description":"What to produce."},"platform":{"type":"string","enum":["blog","instagram","facebook","linkedin","threads","tiktok","youtube","pinterest","bluesky","x"],"description":"Where it will be published. Shapes length, format and tone. All except `blog` and `x` can also be published automatically to a connected account."},"topic":{"type":"string","maxLength":2000,"description":"What it should be about. Free text."},"tone":{"type":"string","maxLength":200,"description":"Overrides the tone learned from the brand."},"num_items":{"type":"integer","minimum":1,"maximum":10,"default":3,"description":"How many separate pieces to produce in this one generation. **Each costs one credit.**"},"num_slides":{"type":"integer","minimum":2,"maximum":10,"description":"Carousels only. Capped at 10 because Instagram and LinkedIn reject more."},"generate_image":{"type":"boolean","description":"Produce images as well as copy. Adds roughly 30–60 seconds."},"image_style":{"type":"string","enum":["product_shot","lifestyle","cinematic","flat_lay","3d_render","minimalist","commercial","typography"],"description":"Visual direction for generated images."},"products":{"type":"array","maxItems":20,"items":{"type":"object"},"description":"Products to feature, each with at least a name. Image URLs are used as visual references."}}},"ResearchInput":{"type":"object","description":"Used when `type` is `research`. Every field is optional.","properties":{"research_areas":{"type":"array","minItems":1,"maxItems":5,"uniqueItems":true,"items":{"type":"string","enum":["audience_analysis","market_gaps","competitive_positioning","viral_trends","content_strategy"]},"description":"Which reports to produce. One report per area. Defaults to audience_analysis, market_gaps and competitive_positioning."},"focus":{"type":"string","maxLength":2000,"description":"Narrows the research. Free text."}}},"Generation":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","const":"generation"},"status":{"type":"string","enum":["running","done","error","cancelled"],"description":"`cancelled` means somebody stopped it deliberately; it is not a failure."},"type":{"type":"string","enum":["content","research"]},"brand_id":{"type":"string"},"progress":{"type":["string","null"],"description":"What the job is doing right now, while running. Null once it has finished."},"warnings":{"type":"array","items":{"type":"string"},"description":"The job produced output but with something reduced — e.g. live web research failed and the report was written without sources. Empty on a clean run."},"result_count":{"type":["integer","null"]},"results":{"type":"array","description":"Present once status is `done`. Image URLs inside are signed and expire after 24 hours; read the generation again for fresh ones.","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","description":"The generated piece: title, body, hashtags, cta, image_url and so on."}}}},"error":{"$ref":"#/components/schemas/ErrorBody"},"created_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"}}},"GenerationList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","description":"Newest first. Each is a Generation without `results`.","items":{"$ref":"#/components/schemas/Generation"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `starting_after` for the next page. Null on the last page."}}},"CreateBrand":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"The brand's website, http or https. Its home page is usually the best choice."},"wait":{"type":"integer","minimum":0,"maximum":120,"default":0,"description":"Seconds to wait for the import to finish before answering. 0 returns an import id immediately."}}},"BrandImport":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","const":"brand_import"},"status":{"type":"string","enum":["running","done","error"]},"url":{"type":"string"},"progress":{"type":["string","null"],"description":"The current step while running."},"brand_id":{"type":["string","null"],"description":"The new brand, once `status` is `done`."},"brand":{"type":["object","null"],"description":"Once done: the new brand as GET /v1/brands lists it. Read its profile with GET /v1/brands/{brand_id}/profile."},"warnings":{"type":"array","items":{"type":"string"},"description":"Parts of the site that could not be read; the brand was still created."},"error":{"$ref":"#/components/schemas/ErrorBody"},"created_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"}}},"BrandProfile":{"type":"object","description":"Version 1 of the brand profile. Every field is always present.","properties":{"object":{"type":"string","const":"brand_profile"},"profile_version":{"type":"integer","const":1,"description":"Changes only on an incompatible change to this shape."},"brand_id":{"type":"string"},"ready":{"type":"boolean","description":"False until the website has been analysed."},"name":{"type":"string"},"website":{"type":["string","null"]},"summary":{"type":"string"},"voice":{"type":"object","properties":{"tone":{"type":"string"},"style":{"type":"string"},"personality":{"type":"array","items":{"type":"string"}},"common_phrases":{"type":"array","items":{"type":"string"}}}},"positioning":{"type":"object","properties":{"one_liner":{"type":"string"},"category":{"type":"string"},"value_proposition":{"type":"string"},"key_benefits":{"type":"array","items":{"type":"string"}},"unique_points":{"type":"array","items":{"type":"string"}},"target_audience":{"type":"array","items":{"type":"string"}}}},"offerings":{"type":"object","properties":{"services":{"type":"array","items":{"type":"string"}},"problems_solved":{"type":"array","items":{"type":"string"}},"outcomes":{"type":"array","items":{"type":"string"}}}},"palette":{"type":"object","description":"Colours as found on the site, usually hex. Null where none was found.","properties":{"primary":{"type":["string","null"]},"secondary":{"type":["string","null"]},"accent":{"type":["string","null"]},"background":{"type":["string","null"]},"text":{"type":["string","null"]},"muted":{"type":["string","null"]}}},"typography":{"type":"object","properties":{"heading":{"type":["string","null"]},"body":{"type":["string","null"]}}},"visual_style":{"type":"object","properties":{"style":{"type":"string"},"visual_tone":{"type":"string"},"image_style":{"type":"string"},"imagery_keywords":{"type":"array","items":{"type":"string"}},"imagery_to_avoid":{"type":"array","items":{"type":"string"}}}},"logo_url":{"type":["string","null"],"description":"The logo as found on the site. An external address, not a Solorva one."},"content_rules":{"type":"object","properties":{"dos":{"type":"array","items":{"type":"string"}},"donts":{"type":"array","items":{"type":"string"}}}},"topics":{"type":"object","properties":{"primary":{"type":"array","items":{"type":"string"}},"secondary":{"type":"array","items":{"type":"string"}}}},"keywords":{"type":"object","properties":{"primary":{"type":"array","items":{"type":"string"}},"secondary":{"type":"array","items":{"type":"string"}}}},"analysed_at":{"type":["string","null"],"format":"date-time","description":"When the website was read."},"reviewed_at":{"type":["string","null"],"format":"date-time","description":"When the customer last edited the profile in the dashboard."},"updated_at":{"type":["string","null"],"format":"date-time"}}},"BrandList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","const":"brand"},"name":{"type":"string"},"website":{"type":["string","null"]},"ready":{"type":"boolean","description":"False until the website has been analysed."},"created_at":{"type":["string","null"],"format":"date-time"}}}}}},"ResizeImage":{"type":"object","required":["content_id"],"description":"Give either a `preset`, or both `width` and `height`.","properties":{"content_id":{"type":"string","description":"Which generated piece to take the image from."},"preset":{"type":"string","enum":["instagram_square","instagram_portrait","instagram_story","tiktok","x_landscape","linkedin_landscape","facebook_landscape","youtube_thumbnail","og_image","pinterest"],"description":"The target channel. Prefer this over width and height."},"width":{"type":"integer","minimum":16,"maximum":4096,"description":"Custom width. Requires height."},"height":{"type":"integer","minimum":16,"maximum":4096,"description":"Custom height. Requires width."},"image_index":{"type":"integer","minimum":0,"default":0,"description":"Which image, when the content has several — a carousel has one per slide."},"fit":{"type":"string","enum":["cover","contain"],"default":"cover","description":"`cover` crops to fill the new shape. `contain` fits the whole image inside it and pads the edges — for when nothing may be cropped, such as a logo."},"background":{"type":"string","default":"#ffffff","description":"Padding colour, used only with `contain`."}}},"RemoveBackground":{"type":"object","required":["content_id"],"properties":{"content_id":{"type":"string","description":"Which generated piece to take the image from."},"image_index":{"type":"integer","minimum":0,"default":0,"description":"Which image, when the content has several."}}},"BrandAssetList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"brand_id":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","const":"brand_asset"},"name":{"type":"string"},"kind":{"type":"string","enum":["logo","product","photo","graphic","other"],"description":"Decides how a generation treats it — a product keeps the fidelity lock, a logo is never redrawn."},"description":{"type":"string"},"used_on_every_generation":{"type":"boolean","description":"Whether this one is attached to each generation. Most assets are not."},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"bytes":{"type":"integer"},"url":{"type":"string","description":"Signed link, valid for 7 days."},"created_at":{"type":"string","format":"date-time"}}}},"url_expires_in_seconds":{"type":"integer"},"limits":{"type":"object","properties":{"max_assets":{"type":"integer"},"max_used_on_every_generation":{"type":"integer"}}}}},"BrandAssetsWrite":{"type":"object","required":["files"],"properties":{"files":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","required":["data"],"properties":{"data":{"type":"string","description":"The image as a data URI: `data:image/png;base64,…`. JPEG, PNG, GIF or WebP, up to 5MB. The bytes are checked against the format's magic number, not the declared type."},"name":{"type":"string","maxLength":120,"description":"What to call it. The filename is a good default."},"kind":{"type":"string","enum":["logo","product","photo","graphic","other"],"default":"other","description":"Changes how a generation treats it: a product keeps the fidelity lock, a logo is never redrawn."},"description":{"type":"string","maxLength":500},"use_on_every_generation":{"type":"boolean","default":false,"description":"Attach this to every generation. Capped — a request asking for more slots than remain is refused with the number."}}}}}},"BrandBrandingWrite":{"type":"object","minProperties":1,"description":"A partial update. Only the slots named are changed.","properties":{"enabled":{"type":"boolean","description":"Whether any of it is applied. Slots can be configured with this off, and nothing is drawn."},"slots":{"type":"object","properties":{"header_left":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}},"header_center":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}},"header_right":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}},"footer_left":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}},"footer_center":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}},"footer_right":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none","text","asset"],"description":"`none` empties this position."},"text":{"type":"string","maxLength":200,"description":"Drawn exactly as written when type is `text`."},"asset_id":{"type":"string","description":"Required when type is `asset`. Must be in this brand's library."}}}}},"style":{"type":"object","properties":{"text_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"bar_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"bar_opacity":{"type":"number","minimum":0,"maximum":1,"description":"0 draws text straight onto the picture."}}}}},"BrandBranding":{"type":"object","properties":{"object":{"type":"string","const":"brand_branding"},"brand_id":{"type":"string"},"enabled":{"type":"boolean","description":"Whether it is actually applied. A configured footer with the switch off draws nothing."},"slots":{"type":"object","description":"The six positions. Each is `none`, `text`, or `asset`.","properties":{"header_left":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}},"header_center":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}},"header_right":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}},"footer_left":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}},"footer_center":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}},"footer_right":{"type":"object","properties":{"type":{"type":"string","enum":["none","text","asset"]},"text":{"type":"string"},"asset_id":{"type":"string"},"asset_name":{"type":"string","nullable":true}}}}},"style":{"type":"object","properties":{"text_color":{"type":"string"},"bar_color":{"type":"string"},"bar_opacity":{"type":"number","minimum":0,"maximum":1}}},"note":{"type":"string"}}},"ImageVariants":{"type":"object","required":["content_id"],"properties":{"content_id":{"type":"string","description":"Which generated piece to take the image from."},"count":{"type":"integer","minimum":1,"maximum":4,"default":2,"description":"How many variants to produce. **One credit each** — this is the only endpoint whose price depends on a parameter."},"image_index":{"type":"integer","minimum":0,"default":0,"description":"Which image, when the content has several."},"instruction":{"type":"string","maxLength":500,"description":"Optional steer for how they should differ — \"wider shot\", \"darker mood\". Leave it out for a straightforward alternative take."}}},"ImageVariantList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","description":"The variants that passed the difference check. May be shorter than `count`.","items":{"$ref":"#/components/schemas/DerivedImage"}},"rejected":{"type":"array","description":"What was discarded and why. None of it was charged for.","items":{"type":"object","properties":{"index":{"type":"integer"},"reason":{"type":"string","enum":["model_error","no_image","not_an_image","too_similar_to_source","duplicate_of_another_variant"]},"detail":{"type":"string"}}}},"model":{"type":"string","description":"Which image model produced them."},"source_content_id":{"type":"string"},"source_image_index":{"type":"integer"},"url_expires_in_seconds":{"type":"integer"},"credits_charged":{"type":"integer","description":"One per variant in `data`. Rejected variants are not counted."}}},"DerivedImage":{"type":"object","properties":{"object":{"type":"string","const":"image"},"url":{"type":"string","description":"Signed link to the new image. Valid for 24 hours — see `url_expires_in_seconds`."},"key":{"type":"string","description":"Storage key for the new object."},"width":{"type":"integer"},"height":{"type":"integer"},"bytes":{"type":"integer"},"format":{"type":"string","enum":["jpg","png"],"description":"PNG whenever the image carries transparency, otherwise JPEG."},"model":{"type":"string","description":"Background removal only: which model produced it."},"difference_from_source":{"type":"integer","minimum":0,"maximum":64,"description":"Variants only. How different this image is from the one it was made from, 0-64. Anything under 3 is rejected as a copy rather than returned."},"source_content_id":{"type":"string"},"source_image_index":{"type":"integer"},"url_expires_in_seconds":{"type":"integer"},"credits_charged":{"type":"integer","description":"0 for a resize, 1 for a background removal, one per variant for variants."}}},"Deck":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","example":"deck"},"title":{"type":"string"},"brand_id":{"type":"string"},"slide_count":{"type":"integer"},"images_generated":{"type":"integer"},"pdf_url":{"type":"string","description":"Signed, and valid for the period in pdf_expires_in_seconds. Read the deck again for a fresh one."},"pdf_expires_in_seconds":{"type":"integer"},"credits_charged":{"type":"integer","description":"Only on creation."},"theme_notes":{"type":"array","items":{"type":"string"},"description":"What Solorva decided about this brand on its behalf — a substituted typeface, a colour changed for contrast. Worth showing somebody: otherwise they meet it in front of a client."},"warnings":{"type":"array","items":{"type":"string"}},"slides":{"type":"array","description":"Slide text. Element geometry and style tokens are deliberately not published: they are an internal contract between the editor and the renderer, and publishing them would make every layout change a breaking API change.","items":{"type":"object","properties":{"number":{"type":"integer"},"layout":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"},"body":{"type":"string"},"has_image":{"type":"boolean"}}}}}},"Credits":{"type":"object","properties":{"object":{"type":"string","const":"credits"},"content_generations":{"$ref":"#/components/schemas/CreditBucket"},"research_generations":{"$ref":"#/components/schemas/CreditBucket"},"allowance_resets_at":{"type":["string","null"],"format":"date-time"}}},"CreditBucket":{"type":"object","properties":{"total":{"type":"integer","description":"allowance + purchased. What can actually be spent."},"allowance":{"type":"integer","description":"From a subscription. Resets each billing period."},"purchased":{"type":"integer","description":"Bought outright. Does not expire."}}},"Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","properties":{"type":{"type":"string","description":"Stable machine-readable classification. Branch on this, not on `message`."},"message":{"type":"string","description":"Human-readable. Wording may change."}}}}}}