{
  "openapi": "3.1.0",
  "info": {
    "title": "Codia API",
    "version": "1.0.0",
    "description": "Codia API provides programmatic access to Codia visual intelligence capabilities. Convert UI screenshots into structured design tokens, extract layouts from PDF documents, generate editable PowerPoint files, vectorize images to SVG, and remove image backgrounds through simple REST endpoints.\n\n## Getting Started\n\n### 1. Buy Codia Open API\n\nAll public Open API endpoints share one Codia Open API balance. [**Buy or subscribe**](https://codia.ai/pricing/open_api/open_api_v2) to the unified API credits SKU once, then use the same credits across screenshot, PDF, image processing, PDF to PPT, and background removal APIs.\n\n### 2. Generate an API Key\n\nGo to [**Personal Center > API Keys**](https://codia.ai/dashboard/developer) and create a key. One key works across all Open API capabilities.\n\n### 3. Install Codia Open API\n\nUse Codia Open API to install these APIs into Codex, Claude, Cursor, and local agent workflows. Install the CLI with `npm install -g @codia/design-skills`, bind an API key with `codia-design auth set --api-key api_key_xxx`, install templates with `codia-design install --platform all`, then verify access with `codia-design credits`. See https://codia.ai/codia-design-skills and https://codia.ai/docs/codia-design-skills.\n\n### 4. Monitor Credits and Usage\n\nUse GET /v2/open/credits to check your remaining balance, GET /v2/open/usage to inspect call-level usage, GET/POST /v2/open/auto_recharge to manage automatic top-ups, and /v2/open/tasks to create, inspect, list, and cancel asynchronous work.\n\n## Agent Workflows\n\nFor agent workflows, use Codia Open API to install Codia Open API tools into Codex, Claude, Cursor, and local agents.\n\n## Figma SDK\n\nFor Figma plugin developers, see the [Figma SDK guide](https://codia.ai/docs/figma-sdk).\n\n## Credit Matrix\n\nCredits are charged from the unified Codia Open API balance. Model prices below come from `products.open_api.tools.*` in `pricing.yaml`; `GET /v2/open/limits` returns the live table.\n\n### Fixed-price operations\n\n| API / operation | Endpoint(s) | Credits |\n| --- | --- | --- |\n| Image to design | `POST /v2/open/image_to_design`, task `image_to_design` | 13 |\n| PDF to design | `POST /v2/open/pdf_to_design`, task `pdf_to_design` | 13 / processed page |\n| Remove background | `POST /v2/open/remove_bg`, task `remove_bg` | 13 |\n| PDF to PPT | `POST /v2/open/pdf_to_ppt`, task `pdf_to_ppt` | 13 / converted page |\n| PDF to PPT reconvert | `POST /v2/open/tasks/{task_id}/reconvert` | 13 / reconverted page |\n| Replace background | `POST /v2/open/image/replace_background`, task `image_replace_background` | 13 |\n| Object erase | `POST /v2/open/image/object_erase`, task `image_object_erase` | 13 |\n| Describe image | `POST /v2/open/image/describe`, task `image_describe` | 5 |\n| Watermark remove | `POST /v2/open/image/watermark_remove`, task `image_watermark_remove` | 13 |\n| Layering | `POST /v2/open/image/layering`, task `image_layering` | 27 |\n| SVG converter create | `POST /v2/open/svg_converter/create` | 13 |\n| SVG converter result/limit | `GET /v2/open/svg_converter/result/{record_id}`, `GET /v2/open/svg_converter/limit` | 0 |\n| Upload / estimate / task get-list-cancel | `/v2/open/uploads`, `/v2/open/estimate`, task status/list/cancel | 0 |\n\n### Text-to-image and image-to-image\n\n`image_generate` charges per generated image and multiplies by `n`. `image_to_image` charges per request. Both use the same model table.\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `seedream_5` | 9 | 9 | 9 | 9 |\n| `seedream_4_5` | 11 | 11 | 11 | 11 |\n| `recraft_v4` | 11 | 11 | 67 | - |\n| `flux_2_pro` | 8 | 8 | 20 | - |\n| `flux_2_max` | 19 | 19 | 43 | - |\n| `ideogram_v3` | 16 | 16 | - | - |\n\nGPT Image quality pricing for `image_generate` and `image_to_image`:\n\n| Quality | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: |\n| `low` | 2 | 9 | 9 |\n| `medium` / omitted / `auto` | 8 | 34 | 34 |\n| `high` | 33 | 133 | 133 |\n\n### Remix\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `gpt_image` | 8 | 8 | 34 | 34 |\n| `seedream_5` | 9 | 9 | 9 | 9 |\n| `seedream_4_5` | 11 | 11 | 11 | 11 |\n| `recraft_v4` | 11 | 11 | 67 | - |\n| `flux_2_pro` | 8 | 8 | 20 | - |\n| `flux_2_max` | 19 | 19 | 43 | - |\n| `ideogram_v3` | 16 | 16 | - | - |\n\n### Reframe\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| `gpt_image` | 8 | 8 | 34 | 34 |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `seedream_5` | 9 | 9 | 9 | 9 |\n| `seedream_4_5` | 11 | 11 | 11 | 11 |\n| `flux_2_pro` | 8 | 8 | 20 | - |\n| `flux_2_max` | 19 | 19 | 43 | - |\n| `ideogram_v3` | 16 | 16 | - | - |\n\n### Upscale\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| default / `codia_image_v2` | 16 | - | - | - |\n| `ideogram_v3` | 16 | 16 | 16 | - |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `gpt_image` | 56 | 56 | 112 | - |\n\nFor `image_generate`, one reference image is included. Extra references: `gpt_image`, `nano_banana_2`, and `nano_banana_pro` add 1 credit per extra reference; `recraft_v4` adds a flat 4 credits when extra references are present; `ideogram_v3` adds a flat 3 credits; Seedream and FLUX extra references are currently free.\n\n## Errors\n\nAll endpoints use the same envelope: `{ code, message, data }`. On success `code = 0`. On failure `code` mirrors the HTTP status code (e.g. `code: 401` for a 401 response).\n\n| HTTP status | When it happens |\n| --- | --- |\n| `400` | Invalid input — missing or malformed parameters, unsupported MIME, page index out of range, etc. |\n| `401` | Missing, invalid, or expired API key. |\n| `402` | Credit balance exhausted. Top up via the dashboard or `/v2/open/auto_recharge`. |\n| `403` | API key not authorised for this endpoint or plan. |\n| `404` | Resource (e.g. task) not found. |\n| `409` | `Idempotency-Key` conflict — same key reused with a different payload. |\n| `410` | Endpoint deprecated and removed. |\n| `429` | Rate limited. Respect the `Retry-After` response header. |\n| `500` | Internal error. Safe to retry once with backoff. |\n\nThe `message` field is a short human-readable description and is not part of any stable contract — do not parse it. Branch on `code` (or the HTTP status) and not on the message text.\n\n## Rate limits\n\nLimits apply per source IP and per API key. When you exceed a limit, the API returns `429 Too Many Requests`. `429` responses **always includes** a `Retry-After` header (in whole seconds) — when present, wait at least that long before retrying.\n\n| Scope | Default limit |\n| --- | --- |\n| All Open API endpoints (per source IP) | 50 requests / second |\n| Task status polling (`GET /v2/open/tasks/{task_id}`, per API key) | 60 requests / minute |\n| Per-model image generation | Varies by model (see model catalog) |\n\nIf you're hitting limits for a legitimate workload, contact support — Enterprise plans can raise these caps.\n\n## Pagination\n\nThis API uses two pagination styles, chosen per endpoint based on how data is most commonly consumed:\n\n- **Offset-based** (`page` + `page_size`) — used by `GET /v2/open/usage` for billing/reporting UIs where users skip to arbitrary pages. The response includes `total` so you can render page numbers.\n- **Cursor-based** (`limit` + `after`) — used by `GET /v2/open/tasks` for sequential consumption of new/changing data. The response includes `has_more`; pass the last item's `created_at` value as the next `after`. No `total` is returned (would be expensive on active task lists).\n\nDo not mix the two styles in your client wrapper — keep separate iterators for each endpoint.\n\n## Environments\n\nThere is no separate sandbox. Every request goes to the production base URL `https://api.codia.ai` — create a real API key and call it directly. Endpoints charge credits only on a successful response, so you can validate your integration with a small balance.",
    "contact": {
      "name": "Codia Support",
      "url": "https://codia.ai/docs/support",
      "email": "service@codia.ai"
    }
  },
  "servers": [
    {
      "url": "https://api.codia.ai",
      "description": "Production (no separate sandbox)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Design Conversion",
      "description": "Convert images and PDFs into structured design data"
    },
    {
      "name": "Presentation Conversion",
      "description": "Convert NotebookLM-style PDF pages into an editable PowerPoint (.pptx) by creating a `pdf_to_ppt` task through the unified Task API. **Important limitation: PDF to PPT currently supports image-only PDFs where every page is a full-page image**, such as NotebookLM-style exported source pages, scanned documents, or screenshot-based PDFs. It is not intended for complex text-flow PDFs, vector-layout PDFs, or mixed-object PDFs. This workflow is useful for NotebookLM PDF to PPT pipelines, screenshot-based source briefings, scanned report-to-PPT automation, and rebuilding PDF slide decks that are already image-only.\n\n## PDF to PPT task workflow\n\n### 1. Prepare a PDF input\n\nUse either `input.upload_id` for a file uploaded to Codia, or `input.pdf_url` for a PDF hosted by your own system. Confirm that the PDF is NotebookLM-style / image-only: each page should be one full-page image. If your source is a normal text PDF, vector PDF, or complex layout PDF, rasterize/export each page to an image first, then package those images into an image-only PDF. If your file is local or private, upload it first with `POST /v2/open/uploads` as `multipart/form-data` field `file`, then pass the returned opaque `upload_id` to `POST /v2/open/tasks`. `/v2/open/uploads` does not return a public file URL.\n\n```bash\ncurl 'https://api.codia.ai/v2/open/uploads' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'file=@./report.pdf'\n```\n\nExample upload response (no public URL is returned):\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"ok\",\n  \"data\": {\n    \"upload_id\": \"upl_550e8400-e29b-41d4-a716-446655440000\",\n    \"filename\": \"report.pdf\",\n    \"size\": 1234567,\n    \"content_type\": \"application/pdf\",\n    \"expires_at\": 1764086400\n  }\n}\n```\n\n### 2. Create the PDF to PPT task\n\nCall `POST /v2/open/tasks` with `operation: \"pdf_to_ppt\"`, either `input.upload_id` or `input.pdf_url`, optional zero-based `input.page_no`, optional `input.title`, and optional top-level `callback_url`. Send an `Idempotency-Key` header if your client may retry the request.\n\n```bash\ncurl 'https://api.codia.ai/v2/open/tasks' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Idempotency-Key: notebooklm-pdf-to-ppt-001' \\\n  --data '{\n    \"operation\": \"pdf_to_ppt\",\n    \"input\": {\n      \"upload_id\": \"upl_550e8400-e29b-41d4-a716-446655440000\",\n      \"page_no\": [0, 1, 2],\n      \"title\": \"NotebookLM source briefing\"\n    },\n    \"callback_url\": \"https://your-server.com/webhooks/codia-task\"\n  }'\n```\n\nExample create response:\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"ok\",\n  \"data\": {\n    \"task_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"operation\": \"pdf_to_ppt\",\n    \"status\": \"pending\",\n    \"created_at\": 1764000000\n  }\n}\n```\n\n### 3. Track progress by task ID\n\nCall `GET /v2/open/tasks/{task_id}` until `status` is `succeeded`, `failed`, or `canceled`.\n\n```bash\ncurl 'https://api.codia.ai/v2/open/tasks/550e8400-e29b-41d4-a716-446655440000' \\\n  -H 'Authorization: Bearer {codia_api_key}'\n```\n\nExample status response while processing:\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"ok\",\n  \"data\": {\n    \"task_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"operation\": \"pdf_to_ppt\",\n    \"status\": \"processing\",\n    \"progress\": 45,\n    \"created_at\": 1764000000,\n    \"updated_at\": 1764000030\n  }\n}\n```\n\n### 4. Receive the webhook instead of polling\n\nFor server-to-server integrations, prefer `callback_url`. Codia sends a signed webhook when the task reaches a terminal state. Verify `X-Codia-Signature` before trusting the payload.\n\n```ts\nimport { createHmac, timingSafeEqual } from 'crypto';\nimport express from 'express';\n\nconst app = express();\napp.use(express.raw({ type: 'application/json' }));\n\napp.post('/webhooks/codia-task', (req, res) => {\n  const signature = String(req.header('X-Codia-Signature') || '').replace(/^sha256=/, '');\n  const expected = createHmac('sha256', process.env.CODIA_API_KEY!.slice(0, 32))\n    .update(req.body)\n    .digest('hex');\n\n  const ok = signature.length === expected.length &&\n    timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));\n  if (!ok) return res.sendStatus(401);\n\n  const event = JSON.parse(req.body.toString('utf8'));\n  if (event.status === 'succeeded') console.log(event.result?.ppt_url);\n  res.sendStatus(200);\n});\n```\n\n### 5. List active jobs or task history\n\nCall `GET /v2/open/tasks` with `operation=pdf_to_ppt`. Use `status=pending,processing` for active jobs, or omit `status` for broader history. Use the response pagination cursor with `after` to fetch the next page.\n\n```bash\ncurl 'https://api.codia.ai/v2/open/tasks?operation=pdf_to_ppt&status=pending,processing&limit=20' \\\n  -H 'Authorization: Bearer {codia_api_key}'\n\ncurl 'https://api.codia.ai/v2/open/tasks?operation=pdf_to_ppt&limit=20&after=550e8400-e29b-41d4-a716-446655440000' \\\n  -H 'Authorization: Bearer {codia_api_key}'\n```\n\n### 6. Cancel pending or processing work\n\nCall `POST /v2/open/tasks/{task_id}/cancel` for a task that is still `pending` or `processing`.\n\n```bash\ncurl -X POST 'https://api.codia.ai/v2/open/tasks/550e8400-e29b-41d4-a716-446655440000/cancel' \\\n  -H 'Authorization: Bearer {codia_api_key}'\n```\n\n### 7. Download the generated PPTX\n\nWhen the task succeeds, read `data.result.ppt_url`, download the PPTX promptly, and store it in your own system if you need long-term access.\n\n```bash\ncurl 'https://api.codia.ai/v2/open/tasks/550e8400-e29b-41d4-a716-446655440000' \\\n  -H 'Authorization: Bearer {codia_api_key}'\n\ncurl -L 'https://static.codia.ai/pptx/notebooklm-source-briefing.pptx' \\\n  -o notebooklm-source-briefing.pptx\n```\n\nSuccessful task shape:\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"ok\",\n  \"data\": {\n    \"task_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"operation\": \"pdf_to_ppt\",\n    \"status\": \"succeeded\",\n    \"progress\": 100,\n    \"result\": {\n      \"ppt_url\": \"https://static.codia.ai/pptx/notebooklm-source-briefing.pptx\"\n    }\n  }\n}\n```\n\n## Input requirement\n\nOnly submit NotebookLM-style image-only PDFs. If each PDF page is not a full-page image, the task may fail with a parse/conversion error or produce incomplete slides. For best results, rasterize each source page to a high-resolution PNG/JPEG page first, then create a PDF from those images and submit that PDF. Use `upload_id` for Codia-managed uploads; use `pdf_url` only for files hosted by your own storage."
    },
    {
      "name": "Image Processing",
      "description": "AI-powered image manipulation endpoints — generate, edit, upscale, describe, and decompose images. All `/v2/open/image/*` endpoints use the unified Codia Open API balance. Credits come from `pricing.yaml`; call `GET /v2/open/limits` for the live price table. Each endpoint accepts an optional `model` parameter; if omitted, a sensible default is used.\n\n### Available Models\n\nThe `model` parameter selects the underlying AI engine. Different models trade off speed, quality, and style — and each charges a different number of credits per call. The table below is the full catalog; the per-endpoint `model` field documents which subset that endpoint supports.\n\n| Model ID | Family | Best for |\n| --- | --- | --- |\n| `nano_banana_2` | Google Gemini 3.1 Flash | Fast, low-cost general image generation and editing. Default for most endpoints. |\n| `nano_banana_pro` | Google Gemini 3 Pro | Studio-quality 4K visuals with advanced reasoning over references and prompts. |\n| `gpt_image` | OpenAI GPT Image 2 | Crisp text rendering inside images, high-fidelity reference handling, flexible sizes. |\n| `seedream_5` | ByteDance Seedream 5 | Next-gen photorealism with fine detail. Sizes are 1920-based instead of 1024-based. |\n| `seedream_4_5` | ByteDance Seedream 4.5 | Reliable, cheap photorealistic generation. Great for portraits. |\n| `recraft_v4` | Recraft V4 | Illustrations, icons, and vector-style art with precise style control. Also does background removal. |\n| `flux_2_pro` | Black Forest Labs FLUX 2 Pro | High-quality general image generation. |\n| `flux_2_max` | Black Forest Labs FLUX 2 Max | Maximum-quality FLUX tier for premium results. |\n| `ideogram_v3` | Ideogram V3 | Excellent typography and creative compositions; supports the widest capability set (generate, remix, reframe, replace background, upscale, describe). |\n| `codia_image_v2` | Codia in-house | Targeted image editing: upscale, object erase, watermark remove, background remove, layering. |\n\nFor text-to-image and image-to-image, the public model list is the same as Studio: `nano_banana_2`, `nano_banana_pro`, `gpt_image`, `seedream_5`, `seedream_4_5`, `recraft_v4`, `flux_2_pro`, `flux_2_max`, `ideogram_v3`.\n\n### Credit Matrix\n\nSee the global Credit Matrix above for every API and model. In short: fixed image operations charge remove_bg 13 (v2/task), replace_background 13, object_erase 13, describe 5, watermark_remove 13, layering 27; model-priced operations are generate_image, image_to_image, remix, reframe, and upscale."
    },
    {
      "name": "Vector Conversion",
      "description": "Convert raster images (PNG, JPG, GIF, WebP) into scalable vector graphics (SVG). Uses the unified Codia Open API balance (13 credits per successful conversion)."
    },
    {
      "name": "Tasks",
      "description": "Create, inspect, list, and cancel asynchronous Open API tasks. Use the Task API instead of the sync equivalents when you need: (a) **webhook delivery** for terminal state; (b) **automatic retry with refund** on failure; (c) **explicit cancellation** of in-flight work; (d) **long-running** operations. \n\n### Quick reference — by operation\n\nPick the row, then decide between the **sync endpoint** (one-call, inline result) and the **Task API** (`POST /v2/open/tasks` with `operation=…`, returns `task_id` for polling/webhook). \"Best as\" reflects the typical case; you can always opt into Task API for retry/webhook semantics.\n\n| operation | Typical time | Best as | Sync endpoint |\n|---|---|---|---|\n| `image_to_design` | ~5s | sync | `POST /v2/open/image_to_design` |\n| `pdf_to_design` | ~5s per page | sync for ≤5 pages, **task** for more | `POST /v2/open/pdf_to_design` |\n| `pdf_to_ppt` | 30s–5min | **task only** — Convert PDF to PPT walkthrough | — |\n| `remove_bg` | ~1s | sync | `POST /v2/open/remove_bg` |\n| `image_generate` | ~10s × `n` | sync for `n=1`, **task** for batches | `POST /v2/open/image/generate_image` |\n| `image_to_image` | ~10s | sync | `POST /v2/open/image/image_to_image` |\n| `image_remix` | ~10s | sync | `POST /v2/open/image/remix` |\n| `image_reframe` | ~10s | sync | `POST /v2/open/image/reframe` |\n| `image_upscale` | ~10s | sync | `POST /v2/open/image/upscale` |\n| `image_object_erase` | ~10s | sync | `POST /v2/open/image/object_erase` |\n| `image_watermark_remove` | ~5s | sync | `POST /v2/open/image/watermark_remove` |\n| `image_layering` | ~5s | sync | `POST /v2/open/image/layering` |\n| `image_describe` | ~10s | sync (LLM-backed; **task** if you need webhooks) | `POST /v2/open/image/describe` |\n| `image_replace_background` | ~10s | sync | `POST /v2/open/image/replace_background` |\n\nDurations are typical wall-clock for a single request and exclude HTTP round-trip / queueing time. Long inputs (high-res images, dense PDFs) extend them. When in doubt, run a small benchmark on your inputs.\n\nWhen you submit a task and your `processing` count for that operation is already at the per-user cap (3 for `pdf_to_ppt`, 5 for everything else), the new task lands in `pending` and **waits in a per-user FIFO queue** until a slot opens. The create response still returns 202 — only `data.status` tells you whether the task is `pending` (queued) or `processing` (running). Each user can hold at most **50 pending+processing tasks per operation**; beyond that the create call returns `429 queue full`."
    },
    {
      "name": "Account",
      "description": "Billing, credit balance, usage history, and auto-recharge configuration."
    },
    {
      "name": "Legacy (v1)",
      "description": "Deprecated v1 Open API endpoints. Retained only for existing integrations and billed on their own independent legacy SKUs. New integrations must use the v2 unified Open API above."
    }
  ],
  "paths": {
    "/v2/open/svg_converter/create": {
      "post": {
        "operationId": "svgConverterCreate",
        "summary": "Convert Image to SVG",
        "description": "**Cost**: 13 credits per successful conversion (failures do not consume credit).\n\nSVG Converter Create supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/svg_converter/create' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./logo.png' \\\n  -F 'file_name=logo.png'\n```\n\nSubmit a raster image (PNG / JPG / GIF / WebP) for vectorization to **SVG**. The conversion runs asynchronously — call this endpoint to enqueue, then poll `GET /v2/open/svg_converter/result/{record_id}` with the returned `record_id` until the SVG is ready.\n\n## What this API is for\n\nAuto-trace a bitmap into clean vector paths so it scales to any resolution without aliasing. Designed for logos, icons, illustrations, and other flat-art assets — not for photographic content (which doesn't vectorize well by nature).\n\nTypical use cases: rebuilding a missing logo source from a PNG export; preparing assets for print or large-format displays; reducing file size for icon sets; preserving crisp edges in design systems.\n\n## Lifecycle\n\nThis is an async, polling-based task (no webhook):\n\n1. **Submit** here. Returns immediately with `data.record_id` (UUID).\n2. **Poll** `GET /v2/open/svg_converter/result/{record_id}` with that `record_id`. The result's `svg_convert_status` transitions `3` (doing) → `1` (complete, with `svg_url`) or `2` (failed).\n3. **Download** the `.svg` from `svg_url` once complete. URLs are pre-signed; fetch promptly and self-host for long-term use.\n\nTypical end-to-end is **5–30 seconds** depending on detail level. Poll every 1–3 seconds.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `pic_url` | yes | Public URL of the raster image (PNG / JPG / GIF / WebP). Must be reachable from Codia's network. |\n| `file_name` | no | Original file name, used only for record-keeping in your dashboard. |\n\n## Plan limits\n\nMaximum image dimensions and file size depend on your subscription plan. Use `GET /v2/open/svg_converter/limit` at runtime to read your live limits — values below are indicative defaults:\n\n| Plan | Max dimensions | Max file size |\n|---|---|---|\n| Free | 512×512 | 512 KB |\n| Starter | 1024×1024 | 2 MB |\n| Pro | 2048×2048 | 4 MB |\n\n## Response\n\nStandard envelope. On 200, `data.record_id` is the UUID you'll need to poll for the result. **Nothing else is returned at this stage** — the SVG itself comes from the `get` endpoint.\n\n## Billing\n\n13 credits per **successful** conversion (deducted when the task completes, not at submission — failed jobs don't consume credit). Uses the unified Codia Open API balance.\n\n## Errors\n\n- `400` — missing `pic_url`, unsupported MIME, or input exceeds your plan's dimensions / file size limits.\n- `401` / `403` — auth issues.\n- `402` — credits exhausted.\n- `429` — rate limit. Back off.\n- `500` — internal error; safe to retry.",
        "tags": [
          "Vector Conversion"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SvgConverterCreateRequest"
              },
              "example": {
                "pic_url": "https://example.com/logo.png",
                "file_name": "logo.png"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "pic_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image (deprecated: use `image_url` instead).",
                    "deprecated": true
                  },
                  "file_name": {
                    "type": "string",
                    "description": "Optional file name"
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the raster image to vectorize when not uploading a file. Canonical field name (preferred over `pic_url`)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversion task accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SvgConverterCreateResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "record_id": "550e8400-e29b-41d4-a716-446655440000"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/svg_converter/result/{record_id}": {
      "get": {
        "operationId": "svgConverterGetResult",
        "summary": "Get SVG Conversion Result",
        "description": "Poll for the result of an image-to-SVG conversion task submitted via `POST /v2/open/svg_converter/create`. Returns either an interim status (conversion still running) or a terminal state (complete with download URL, or failed).\n\n## When to call\n\nPoll every **1-3 seconds** until `svg_convert_status` is complete or failed. Typical end-to-end time is **5-30 seconds** depending on image complexity and detail level. Longer runs without progress usually mean the input is unsuitable or the job should be retried.\n\n## Status values\n\n| `svg_convert_status` | Meaning | What `data` carries |\n|---|---|---|\n| `1` | Complete. | Adds `svg_url` with the generated SVG download link. |\n| `2` | Failed. | No SVG URL; show a recoverable error and let the user retry with a cleaner input. |\n| `3` | Doing. | Conversion is still in progress; keep polling with backoff. |\n\n## Download semantics\n\n`svg_url` points to a generated SVG asset. Download promptly and self-host the file if your app needs long-term access. The SVG content is plain XML, so callers may also fetch and parse it inline when persistence is not needed.\n\n## Errors\n\n- `400` — `record_id` missing or malformed.\n- `401` — missing, invalid, or unauthorized API key.\n- `404` — conversion record not found for this API key.\n- `429` — polling too aggressively. Back off and respect `Retry-After` when present.\n- `500` — transient internal error; retry once with backoff.",
        "tags": [
          "Vector Conversion"
        ],
        "parameters": [
          {
            "name": "record_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Record ID returned by `POST /v2/open/svg_converter/create`."
          }
        ],
        "responses": {
          "200": {
            "description": "Conversion status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SvgConverterGetResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "svg_url": "https://cdn.codia.ai/svg/result.svg",
                    "svg_convert_status": 1
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/svg_converter/limit": {
      "get": {
        "operationId": "svgConverterLimitGet",
        "summary": "Get SVG Conversion Limits",
        "description": "Returns the maximum input image dimensions and file size your **current subscription plan** allows for `/v2/open/svg_converter/create`, plus the credit cost per conversion. Use this endpoint to validate user uploads before submitting conversion work, or to surface plan limits in your own UI.\n\n## Response\n\n| Field | Unit | Meaning |\n|---|---|---|\n| `pic_height` | px | Maximum allowed height of the input raster image. |\n| `pic_width` | px | Maximum allowed width of the input raster image. |\n| `file_size` | bytes | Maximum allowed file size, measured on the raw uploaded file, not base64 text. |\n| `layer_credit_unit` | credits | Credits charged per successful conversion. Today this is usually `13`. |\n\nAll values reflect live plan state. If the user upgrades or downgrades during a session, call this endpoint again before accepting new uploads.\n\n## Errors\n\n- `401` — missing, invalid, or unauthorized API key.\n- `500` — internal error.",
        "tags": [
          "Vector Conversion"
        ],
        "responses": {
          "200": {
            "description": "Plan limits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SvgConverterLimitResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "pic_height": 2048,
                    "pic_width": 2048,
                    "file_size": 4194304,
                    "layer_credit_unit": 1
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/image/upscale": {
      "post": {
        "operationId": "imageUpscale",
        "summary": "Upscale Image",
        "description": "**Cost**: Model-priced per successful call. See the Credit Matrix for `image_upscale` model and resolution prices.\n\nImage Upscale supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/upscale' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png'\n```\n\nEnlarge an image to a higher resolution while reconstructing detail (not just bilinear interpolation). Model-driven — different models trade off sharpness, smoothness, and processing time. Synchronous.\n\n## What this API is for\n\nRecovering print-quality assets from low-resolution sources, preparing low-res user uploads for downstream tasks (e.g. background removal that needs more pixels), or salvaging legacy thumbnails. Use it before any pipeline step that benefits from more pixels — don't chain it after a generative step (those models already output at their target resolution).\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the image to upscale. |\n| `model` | no | Model ID. Defaults to `codia_image_v2` (fast, balanced). Also available: `ideogram_v3` (sharper edges, slower). |\n\nUpscale factor is determined by the model, not a request parameter — typically 2× to 4×. Resulting dimensions are returned implicitly via the output URL.\n\n## Response\n\nShared `ImageUrlListSuccess` shape — `data.image_urls` is an array of CDN URLs (length 1 for this endpoint). Use the first entry.\n\n```json\n{ \"code\": 0, \"message\": \"ok\", \"data\": { \"image_urls\": [\"https://cdn.codia.ai/upscale/xyz.png\"] } }\n```\n\n## Billing & limits\n\n- Credits follow the `image_upscale` model/resolution table in the global Credit Matrix.\n- Synchronous. Latency scales with input size; very large inputs may approach minute-scale.\n\n## Errors\n\nStandard set — `400` bad input, `401`/`403` auth, `402` credits, `429` rate limit, `500` internal.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_upscale` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageUpscaleRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg",
                "model": "codia_image_v2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/replace_background": {
      "post": {
        "operationId": "imageReplaceBackground",
        "summary": "Replace Background",
        "description": "**Cost**: 13 credits per successful call.\n\nImage Replace Background supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/replace_background' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./product.png' \\\n  -F 'prompt=clean white studio background'\n```\n\nKeep the foreground subject of an image and synthesize a new background described by a text prompt. Synchronous, single-call. Combines background removal + inpainting + generative rendering in one step.\n\n## What this API is for\n\nReshooting product photography against new scenes (\"on a marble counter\", \"in front of a sunset window\"), reusing portraits across campaigns, or any time you want to keep the subject and change its environment. The subject is preserved; the prompt only steers the new background.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the source image. |\n| `prompt` | yes | Natural-language description of the desired new background. Be specific about lighting, surface, and context for best results. |\n| `model` | no | Model ID. Defaults to `nano_banana_2`. Also: `nano_banana_pro` (higher fidelity), `ideogram_v3` (alternative aesthetic). |\n\n**Prompt tips**: describe the surface or scene (\"clean white marble table\"), the lighting (\"soft studio lighting from the left\"), and the mood. Avoid contradicting the subject's lighting unless you want the subject re-lit (the model may match it).\n\n## Response\n\nShared `ImageUrlListSuccess` shape — `data.image_urls` is an array of CDN URLs (length 1).\n\n## Billing & limits\n\n- 13 credits per request from the unified Codia Open API balance.\n- Synchronous, single-digit-second latency for typical inputs.\n\n## Errors\n\nStandard set — `400` bad input (missing `image_url` or `prompt`), `401`/`403` auth, `402` credits, `429` rate limit, `500` internal.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_replace_background` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageReplaceBackgroundRequest"
              },
              "example": {
                "image_url": "https://example.com/product.jpg",
                "prompt": "Place the product on a clean marble table with soft studio lighting",
                "model": "nano_banana_2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Replacement background prompt"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/object_erase": {
      "post": {
        "operationId": "imageObjectErase",
        "summary": "Erase Object",
        "description": "**Cost**: 13 credits per successful call.\n\nImage Object Erase supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/object_erase' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./photo.png' \\\n  -F 'mask=@./mask.png'\n```\n\nRemove specific regions of an image as identified by a **mask** image, and inpaint the gap so the result looks like the object was never there. Synchronous.\n\n## What this API is for\n\nDeleting people from background plates, removing unwanted objects from product shots, cleaning up screenshots before redistribution. Unlike `watermark_remove` (which auto-detects), this endpoint requires you to **specify exactly what to erase** via a binary mask.\n\n## Mask format\n\n`mask_url` must point to an image with the **same dimensions** as `image_url`:\n\n- **White (255)** pixels = area to erase + inpaint.\n- **Black (0)** pixels = keep as-is.\n- Grayscale values in between produce partial blending; for crisp removal, threshold your mask to pure black/white.\n\nThe mask can be a separate PNG you generated client-side (e.g. from a brush UI or a segmentation model) or an alpha-channel cutout. Soft edges in the mask produce softer transitions in the result.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the source image. |\n| `mask_url` | yes | Public URL of the mask image (same dimensions as source). |\n| `model` | no | Model ID. Defaults to `codia_image_v2`. Also: `nano_banana_2`, `nano_banana_pro` (higher fidelity at higher latency). |\n\n## Response\n\nShared `ImageUrlListSuccess` — `data.image_urls` carries one CDN URL of the cleaned image.\n\n## Billing & limits\n\n- 13 credits per request from the unified Codia Open API balance.\n- Synchronous.\n\n## Errors\n\nStandard set; in particular `400` on size mismatch between image and mask.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_object_erase` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageObjectEraseRequest"
              },
              "example": {
                "image_url": "https://example.com/photo.jpg",
                "mask_url": "https://example.com/mask.png",
                "model": "codia_image_v2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image",
                  "mask"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "mask": {
                    "type": "string",
                    "format": "binary",
                    "description": "Mask image file"
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "mask_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Mask image URL when not uploading mask"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/describe": {
      "post": {
        "operationId": "imageDescribe",
        "summary": "Describe Image",
        "description": "**Cost**: 5 credits per successful call.\n\nImage Describe supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/describe' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png'\n```\n\nGet a natural-language description of what's in an image — the only non-image-output endpoint in the Image Processing family. Synchronous.\n\n## What this API is for\n\nAuto-captioning for accessibility / SEO, classification or moderation pre-screening, building alt-text at scale, feeding image context into a downstream text-only LLM. Output is a single English sentence-or-paragraph describing subject, scene, and notable details — **not** a structured tagging output. For structured layout extraction, use `image_to_design` instead.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the image to describe. |\n| `model` | no | Model ID. Defaults to `nano_banana_2`. Also: `nano_banana_pro` (more detailed), `ideogram_v3`. |\n\n## Response\n\nUnique to this endpoint (not `ImageUrlListSuccess`): `data.description` is a single string.\n\n```json\n{ \"code\": 0, \"message\": \"ok\", \"data\": { \"description\": \"A red ceramic mug on a wooden desk beside an open notebook…\" } }\n```\n\n## Billing & limits\n\n- **5 credits per request** (higher than most image endpoints — vision-language model cost).\n- Synchronous.\n\n## Errors\n\nStandard set.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_describe` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageDescribeRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg",
                "model": "nano_banana_2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Image description",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageDescribeResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "description": "A product photo on a bright studio background."
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/watermark_remove": {
      "post": {
        "operationId": "imageWatermarkRemove",
        "summary": "Remove Watermark",
        "description": "**Cost**: 13 credits per successful call.\n\nImage Watermark Remove supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/watermark_remove' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png'\n```\n\nDetect and remove visible watermarks from an image — text overlays, logos, repeated patterns, corner stamps. Auto-detection (no mask required). Synchronous.\n\n> ⚠️ **Use responsibly.** Only call this endpoint on images you own or have explicit permission to edit. Removing watermarks from third-party content may violate copyright or terms of service.\n\n## What this API is for\n\nRecovering pre-watermarked archive footage, removing automatic camera stamps from your own captures, cleaning up assets where you hold the rights but only have the watermarked export. Not for laundering stock photography or competitor content.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the watermarked image. |\n\nNo `model` parameter — detection is automatic.\n\n## Response\n\nShared `ImageUrlListSuccess` — `data.image_urls` carries one URL of the cleaned image. If no watermark is detected, the original image (or a near-identity copy) is returned.\n\n## Billing & limits\n\n- 13 credits per request from the unified Codia Open API balance.\n- Synchronous.\n\n## Errors\n\nStandard set.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_watermark_remove` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageWatermarkRemoveRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/generate_image": {
      "post": {
        "operationId": "imageGenerate",
        "summary": "Generate Image",
        "description": "**Cost**: configured per generated image in `pricing.yaml` (multiplied by `n`).\n\nText-to-image generation. Produce one or more images from a natural-language prompt, optionally steered by reference images for style or composition. Synchronous; routes through whichever model you pick.\n\n## What this API is for\n\nThe canonical \"generate image from text\" endpoint. Use it for hero art, marketing assets, agent-driven illustration, batched A/B variants. Reference images give you style/subject control without fine-tuning.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `prompt` | yes | Text description of the image to generate. The most important field — be specific about subject, composition, lighting, and style. |\n| `n` | no | Number of images to generate. Default `1`. **Credits are charged per generated image**, not per call. |\n| `size` | no | Output dimensions as `WIDTHxHEIGHT`, e.g. `1024x1024`. Defaults vary by model. Common: `1024x1024`, `1536x1024`, `1024x1536`. |\n| `model` | no | Model ID. Default `nano_banana_2`. Available: `nano_banana_2`, `nano_banana_pro`, `gpt_image`, `seedream_5`, `seedream_4_5`, `recraft_v4`, `flux_2_pro`, `flux_2_max`, `ideogram_v3`. Each has its own aesthetic / strength — see the table below. |\n| `quality` | no | Quality preset (model-dependent). For `gpt_image`: `low` / `medium` / `high` / `auto`. Omitted/`auto` bills as the medium bucket. Ignored by other models. |\n| `background` | no | Background mode (model-dependent). For `gpt_image`: `transparent` / `opaque` / `auto`. |\n| `reference_images` | no | Array of public image URLs to steer the generation. Most models accept up to a handful — semantics vary (style ref vs. subject ref). |\n\n### Model cheat-sheet\n\n| Model | Strength |\n|---|---|\n| `nano_banana_2` (default) | Fast, general-purpose, good prompt adherence. |\n| `nano_banana_pro` | Higher fidelity at higher latency. |\n| `seedream_5` / `seedream_4_5` | Cinematic, photographic. |\n| `flux_2_pro` / `flux_2_max` | High-quality, very precise prompt following. |\n| `gpt_image` | Best for text inside images and structured layouts. Supports `quality` and `background`. |\n| `recraft_v4` | Vector-friendly art / illustration. |\n| `ideogram_v3` | Strong typography, posters, logos. |\n\n## Response\n\nShared `ImageUrlListSuccess` — `data.image_urls` is an array of CDN URLs, length `n`.\n\n## Billing\n\n**Credits charged per generated image**, not per call. For `n = 4`, the configured per-image model price is multiplied by 4. Check `GET /v2/open/limits` for the live per-operation price table and `GET /v2/open/usage` for charged calls. Uses the unified Codia Open API balance.\n\n## Errors\n\n- `400` — missing/empty `prompt`, invalid `size` format, unknown `model`, too many `reference_images`.\n- `401` / `403` — auth.\n- `402` — credits exhausted.\n- `429` — rate limit.\n- `500` — internal; safe to retry.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline. When `n > 1` the call generates images sequentially and may approach HTTP timeouts; prefer `POST /v2/open/tasks` with `operation=image_generate` for batch jobs (and for webhook delivery / retry semantics). Inputs and per-image credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageGenerateRequest"
              },
              "example": {
                "prompt": "A minimal app icon for a design automation platform",
                "n": 1,
                "size": "1024x1024",
                "model": "nano_banana_2"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/image_to_image": {
      "post": {
        "operationId": "imageToImage",
        "summary": "Image to Image",
        "description": "**Cost**: configured in `pricing.yaml` (model-dependent — see `model` field).\n\nImage to Image supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/image_to_image' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png' \\\n  -F 'prompt=make it watercolor'\n```\n\nTransform a source image into a new one steered by a text prompt — \"this photo, but Studio Ghibli style\", \"this product shot, but at sunset\", \"this sketch, refined into a finished painting\". Synchronous. Different from `remix` (which preserves more structure) and `replace_background` (which only changes the background).\n\n## What this API is for\n\nStyle transfer, refinement, restylization, scene editing — any task framed as \"take this image and change it according to this instruction\". Holds onto the source's overall composition more loosely than `remix` — use that one when you need tighter structural preservation.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the source image. |\n| `prompt` | yes | Text description of the desired transformation. |\n| `model` | no | Default `nano_banana_2`. Same model menu as `generate_image`: `nano_banana_2`, `nano_banana_pro`, `gpt_image`, `seedream_5`, `seedream_4_5`, `recraft_v4`, `flux_2_pro`, `flux_2_max`, `ideogram_v3`. |\n| `size` | no | Output dimensions as `WIDTHxHEIGHT`. For `gpt_image`, size is normalized into the pricing resolution bucket (`1K`, `2K`, `4K`). |\n| `quality` | no | GPT Image only: `low`, `medium`, `high`, or `auto`. Omitted/`auto` bills as the medium bucket. Ignored by non-GPT models. |\n| `background` | no | GPT Image only: `transparent`, `opaque`, or `auto`. Ignored by non-GPT models. |\n\n## Response\n\nShared `ImageUrlListSuccess` — `data.image_urls` carries one URL of the transformed image.\n\n## Billing & limits\n\n- Credits come from `pricing.yaml` for the selected model. For `gpt_image`, the billing key combines resolution and quality, e.g. `1K_high`; call `GET /v2/open/limits` for live values.\n- Synchronous, single-call.\n\n## When to pick what\n\n- Want a complete transformation? → `image_to_image`.\n- Want to keep structure but change style? → `remix`.\n- Want to keep subject and only change scene? → `replace_background`.\n- Want to start from scratch with optional refs? → `generate_image`.\n\n## Errors\n\nStandard set.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_to_image` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageToImageRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg",
                "prompt": "Make the scene warmer and more premium",
                "model": "nano_banana_2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Edit prompt"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/remix": {
      "post": {
        "operationId": "imageRemix",
        "summary": "Remix Image",
        "description": "**Cost**: Model-priced per successful call. See the Credit Matrix for `image_remix` model and resolution prices.\n\nImage Remix supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/remix' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png' \\\n  -F 'prompt=turn into pixel art' \\\n  -F 'size=1024x1024'\n```\n\nGenerate a new image that **preserves the structure** of a source (composition, layout, pose, key shapes) while changing the rest according to a prompt. Stronger structural preservation than `image_to_image` — use this when the silhouette / framing matters and only the surface should change.\n\n## What this API is for\n\nThumbnail variants that keep the same crop; alternate color schemes for the same composition; style variants for marketing across regions; iterating on a moodboard image.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the source. |\n| `prompt` | yes | Description of the desired remix. |\n| `model` | no | Default `nano_banana_2`. Also: `nano_banana_pro`, `ideogram_v3`. |\n| `size` | no | Output dimensions as `WIDTHxHEIGHT`. If omitted, output matches the source's aspect. |\n\n## Response\n\nShared `ImageUrlListSuccess` — one URL in `data.image_urls`.\n\n## Billing & limits\n\n- Credits follow the `image_remix` model/resolution table in the global Credit Matrix.\n- Synchronous.\n\n## Errors\n\nStandard set.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_remix` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageRemixRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg",
                "prompt": "Turn this into a clean SaaS landing page hero",
                "model": "nano_banana_2",
                "size": "1024x1024"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Remix prompt"
                  },
                  "size": {
                    "type": "string",
                    "description": "Output size"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/reframe": {
      "post": {
        "operationId": "imageReframe",
        "summary": "Reframe Image",
        "description": "**Cost**: Model-priced per successful call. See the Credit Matrix for `image_reframe` model and resolution prices.\n\nImage Reframe supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/reframe' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png' \\\n  -F 'resolution=1024x1024'\n```\n\nOutpaint / extend an image to fill a larger canvas at a specified target resolution — generate new pixels on the outside of the original frame rather than scaling it up. Synchronous.\n\n## What this API is for\n\nConverting square assets to wide banner formats, extending portraits into landscape, repurposing a 1:1 product photo as a 16:9 hero image. Different from `upscale` (which adds pixels *inside* the same crop) — `reframe` adds pixels *outside* it.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the source. |\n| `resolution` | yes | Target dimensions as `WIDTHxHEIGHT`, e.g. `1920x1080`. The output image will be exactly this size, with the source placed inside and new content generated to fill the rest. |\n| `model` | no | Default `nano_banana_2`. Also: `nano_banana_pro`, `ideogram_v3`. |\n\nIf `resolution` matches the source's aspect ratio, the result is effectively an upscale (use `upscale` instead). The endpoint shines when the **aspect ratio changes** and net new pixels need to be hallucinated.\n\n## Response\n\nShared `ImageUrlListSuccess` — one URL.\n\n## Billing & limits\n\n- Credits follow the `image_reframe` model/resolution table in the global Credit Matrix.\n- Synchronous.\n\n## Errors\n\nStandard set; `400` on malformed `resolution`.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_reframe` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageReframeRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg",
                "resolution": "1920x1080",
                "model": "nano_banana_2"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  },
                  "resolution": {
                    "type": "string",
                    "description": "Target resolution"
                  },
                  "model": {
                    "type": "string",
                    "description": "Optional model"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ImageUrlListSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image/layering": {
      "post": {
        "operationId": "imageLayering",
        "summary": "Layer Image",
        "description": "**Cost**: 27 credits per successful call.\n\nImage Layering supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image/layering' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./image.png'\n```\n\nDecompose a single image into an **ordered stack of editable layers** — background, subject(s), and overlays — each rendered separately. The output is a JSON DSL describing the layer tree plus the per-layer image data. Use this when you want a designer-style PSD-equivalent, not a single flat composition.\n\n## What this API is for\n\nTurning a flat marketing image back into editable components; pre-processing for downstream compositing; setting up animated reveals where each layer animates separately; segmenting photo content for fine-grained editing.\n\nDifferent from `image_to_design`: that one returns a structural design-tree (Body / Layer / Text / Image nodes with positions and styles). `image_layering` returns visual layers (independent rendered images that you can stack). They complement each other.\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the image to decompose. |\n\nNo `model` parameter — uses Codia's layering pipeline.\n\n## Response\n\nUnique to this endpoint:\n\n| Field | Type | Meaning |\n|---|---|---|\n| `data.type` | integer | Result-type code. Identifies the schema variant of `dsl`. |\n| `data.dsl` | string | **JSON-encoded** layer tree. Parse client-side. Contains layer metadata + URLs to the rendered layer images. |\n\n```json\n{ \"code\": 0, \"message\": \"ok\", \"data\": { \"type\": 1, \"dsl\": \"{\\\"layers\\\":[…]}\" } }\n```\n\nThe `dsl` field is intentionally a string (not a nested object) so you can store it verbatim or stream it without re-encoding.\n\n## Billing & limits\n\n- 27 credits per request from the unified Codia Open API balance.\n- Synchronous.\n\n## Errors\n\nStandard set.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_layering` instead. Inputs and credit cost are identical.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageLayeringRequest"
              },
              "example": {
                "image_url": "https://example.com/image.jpg"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Source image URL when not uploading image"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Layered image result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageLayeringResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "type": 1,
                    "dsl": "{\"elementId\":\"root_001\"}"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/image_to_design": {
      "post": {
        "operationId": "imageToDesignV2",
        "summary": "Convert Image to Design",
        "description": "**Cost**: 13 credits per successful call.\n\nTurn a single UI screenshot into an editable, hierarchical design tree — the same representation Codia Studio uses internally and the foundation that downstream code-generation, Figma-import, and layer-decomposition workflows are built on.\n\n## Request options\n\nThis endpoint supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with `image_url` when the image is already publicly reachable.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nJSON example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -H 'Content-Type: application/json' \\\n  --data '{ \"image_url\": \"https://example.com/screenshot.png\" }'\n```\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/image_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./screenshot.png'\n```\n\n## What this API is for\n\nGiven a public image URL of a UI (mobile screen, desktop page, dashboard, marketing hero — anything pixel-based), the service runs detection + layout reconstruction and returns a tree of **`VisualElement`** nodes that mirror how a designer would have built the screen in Figma or Sketch:\n\n- Every visible region becomes a node with a position, a size, and a style.\n- Container/child relationships are preserved (e.g. a card holds its title, body, and CTA).\n- Layout intent (Flex vs. Absolute), text typography (font, size, weight, color), shapes, images, and effects are all reified into structured fields — not flattened into a single bitmap.\n\nTypical use cases:\n- **Image → code / image → Figma**: walk the tree and emit React / HTML / Figma nodes.\n- **Design QA**: diff a screenshot against a spec by comparing structured trees.\n- **Layer extraction**: feed the tree into downstream tools (e.g. our PDF/PPT pipeline or your own renderer).\n- **Agent / LLM workflows**: give a model a structured layout instead of raw pixels so it can reason about \"the price label sits inside the product card.\"\n\n## How the response is shaped\n\nThe response uses the standard Codia envelope (`code`, `message`, `data`). On success `code = 0` and `data` contains two fields:\n\n| Field | What it is | How to use it |\n|---|---|---|\n| `data.configuration` | Global rendering settings — `baseWidth`, `measurementUnit` (`px` \\| `pt`), `scalingFactor`. | Multiply every numeric value in the tree by `scalingFactor` when rendering. Treat `baseWidth` as the canvas width the tree was measured against (375 for mobile, 1440 for desktop, etc.). |\n| `data.visualElement` | The **root** of the visual tree. Always an `elementType: \"Body\"` node whose `childElements` recursively contain the rest of the page. | Traverse depth-first. The fields you care about per node depend on `elementType` (see below). |\n\n## Walking the `VisualElement` tree\n\nEach node has the same shape, but a handful of fields are populated based on `elementType`:\n\n| `elementType` | Populated content fields | Typical meaning |\n|---|---|---|\n| `Body` | `layoutConfig`, `styleConfig` | Root of the page / artboard |\n| `Layer` | `layoutConfig`, `styleConfig` | Generic container (card, section) |\n| `Group` | `childElements` | Pure grouping, no own visuals |\n| `Text` | `styleConfig.textConfig`, `contentData.textValue` | A run of text |\n| `Image` | `contentData.imageSource` | Raster image (URL) |\n| `Vector` | `contentData.vectorData` | Vector shape (mostly PDF; image_to_design uses `Image` / `Layer` for shapes) |\n| `Component` | `componentSpec`, `contentData.componentReference` | Reusable detected component |\n\nFor every node:\n- **`elementId` / `elementName`** — stable identifier + human label. Use `elementId` as your React `key` or Figma node id.\n- **`childElements`** — array of nested `VisualElement`s. Empty leaf for text/image. Recurse here.\n- **`processingMeta.detectionScore`** — confidence in `[0, 1]`. Filter or warn on low-confidence nodes when generating production code.\n- **`processingMeta.surfaceArea`** — pixel area; useful for sorting or pruning tiny artifacts.\n\n### `layoutConfig` — where the node sits\n\n- `positionMode`:\n  - `\"Flex\"` — the node participates in its parent's flex layout. Read `flexAttributes` (`flexDirection`, `alignItems`, `justifyContent`, `flexWrap`) on the **parent** to know how children flow.\n  - `\"Absolute\"` — positioned at exact coordinates. Read `absoluteAttrs.coord` for the offset relative to the parent.\n  - `\"Normal\"` / `\"Relative\"` — document-flow positioning (rare on this endpoint; common at the root).\n- `flexibleMode` — present on containers, identifies the flex variant the detector chose.\n\nIn practice, the root `Body` is `Flex` and most children inherit Flex from their parent, so a naive emitter that maps `positionMode` → CSS `display: flex` + `position: absolute` will reproduce the layout faithfully.\n\n### `styleConfig` — what the node looks like\n\nThis is the largest object in the response. Group the fields by what they describe:\n\n- **Size** — `widthSpec`, `heightSpec`. Each is a `DimensionSpec` with `sizing` (`FIXED` = exact px, `FILL` = stretch to parent, `FIT_CONTENT` = shrink to children) and a numeric `value`. Map `FIXED → width: Npx`, `FILL → flex: 1` (or `width: 100%`), `FIT_CONTENT → width: auto`.\n- **Text** (on `Text` nodes) — `textConfig.{fontSize, fontStyle, fontFamily, lineHeight, letterSpacing, textAlign}` + `textColor` (a `VisualColor` with `rgbValues` and `hexCode`). `letterSpacing` is in CSS px against the detected font-size; Studio converts to a percentage at import time (Figma semantics).\n- **Border** — `borderConfig.{borderWidth, borderStyle, borderColor, borderRadius}`. `borderRadius` is an array `[tl, tr, br, bl]`; collapse to a single value if all four are equal.\n- **Background** — `backgroundConfig` is a `oneOf` with three shapes discriminated by `type`:\n  - `\"COLOR\"` → solid fill via `backgroundColor`.\n  - `\"IMAGE\"` → `imageUrl` + CSS-style `backgroundSize` / `backgroundPosition`.\n  - `\"LINEAR_GRADIENT\"` → `deg` + `gradientStops[]` (each stop = `{color, position}` where `position` is 0–1).\n- **Effects / transforms** — `effectsList` (shadows, blurs), `opacityLevel` (0–255 — divide by 255 for CSS), `rotationAngle` (degrees), `paddingValues`, `overflowMode`.\n\nFields tagged \"PDF only\" in the schema (`characterData`, `transformMatrix`, `cornerRadius`, `isRightToLeft`, `extendedMatrix`) are not produced by image_to_design — they're shared with `pdf_to_design` and you can ignore them here. Same for `boundingBox`, `displayOrder`, and `contentData.{svgTemplate, imageData, maskingMode, blendingType}`.\n\n### `contentData` — what the node displays\n\nOnly populated on leaf-ish nodes:\n- `textValue` — the actual string for `Text` nodes.\n- `imageSource` — public URL for `Image` nodes (re-hosted on Codia's CDN; safe to embed).\n- `componentReference` / `componentAttributes` — pointer + props for detected components, paired with `componentSpec` on the same node.\n\n## A minimal traversal\n\n```ts\nfunction walk(node: VisualElement, depth = 0) {\n  console.log('  '.repeat(depth), node.elementType, node.elementId,\n    node.elementType === 'Text' ? node.contentData?.textValue :\n    node.elementType === 'Image' ? node.contentData?.imageSource : '');\n  node.childElements?.forEach(child => walk(child, depth + 1));\n}\nwalk(response.data.visualElement);\n```\n\n## Inputs, limits, billing\n\n- **Input** — either JSON with a single `image_url` pointing to a publicly fetchable PNG / JPG / WebP, or `multipart/form-data` with an `image` file. For direct upload, Codia checks credits before reading and uploading the file. Field name `file` is also accepted for compatibility. Maximum side length and file-size limits follow the global Codia API limits.\n- **Billing** — consumes the unified Codia Open API balance (see `GET /v2/open/credits`). 13 credits per successful call.\n- **Latency** — synchronous; typical end-to-end is single-digit seconds for mobile screens, longer for dense desktop pages.\n- **Idempotency** — same image URL produces the same tree modulo `elementId` regeneration; do not rely on stable IDs across calls.\n\n## Errors\n\nUses the standard envelope with non-zero `code` and a descriptive `message`. HTTP status codes:\n- `400` — bad input (missing/invalid `image_url`, unreachable URL, unsupported MIME).\n- `401` / `403` — missing / wrong / unauthorized API key.\n- `402` — credit balance exhausted (top up via the dashboard or `/v2/open/auto_recharge`).\n- `429` — rate-limited; back off and retry.\n- `500` — internal error; safe to retry once.\n\n### `measurementUnit`\n\nAlways `px` for `image_to_design` — screen captures and rasterised UI are inherently pixel-based.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_to_design` instead. Inputs and credit cost are identical to this v2 endpoint.",
        "tags": [
          "Design Conversion"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "image_url"
                ],
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the image to analyze. Must be a directly fetchable PNG, JPG, or WebP — pre-signed S3 or CDN URLs are fine, but pages that gate the asset behind login or JavaScript will fail. There is no separate `image_base64` field; host the image first."
                  }
                }
              },
              "example": {
                "image_url": "https://example.com/screenshot.png"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Local image file to analyze. Use this when the image is not already hosted. The server checks credits before reading and uploading the file. Field name `file` is also accepted for compatibility."
                  }
                }
              },
              "encoding": {
                "image": {
                  "contentType": "image/png, image/jpeg, image/webp, image/gif"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful conversion. `data.visualElement` is the root `Body` node of the tree — recurse through `childElements` to reach every detected region. `data.configuration` carries the canvas base width, unit, and a scale factor you should multiply every numeric value by before rendering. See the operation description above for a full walkthrough of `elementType`, `layoutConfig`, `styleConfig`, and `contentData`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageToDesignResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "configuration": {
                      "baseWidth": 375,
                      "measurementUnit": "px",
                      "scalingFactor": 1
                    },
                    "visualElement": {
                      "elementId": "root_001",
                      "elementName": "Page",
                      "elementType": "Body",
                      "layoutConfig": {
                        "positionMode": "Normal",
                        "flexibleMode": "Flex",
                        "flexAttributes": {
                          "flexDirection": "column",
                          "alignItems": "stretch",
                          "justifyContent": "flex-start"
                        }
                      },
                      "styleConfig": {
                        "widthSpec": {
                          "sizing": "FIXED",
                          "value": 375
                        },
                        "heightSpec": {
                          "sizing": "FIXED",
                          "value": 812
                        },
                        "backgroundConfig": {
                          "type": "COLOR",
                          "backgroundColor": {
                            "rgbValues": [
                              255,
                              255,
                              255
                            ],
                            "hexCode": "#FFFFFF"
                          }
                        }
                      },
                      "processingMeta": {
                        "surfaceArea": 304500,
                        "detectionScore": 0.96
                      },
                      "childElements": [
                        {
                          "elementId": "header_001",
                          "elementName": "Header",
                          "elementType": "Layer",
                          "layoutConfig": {
                            "positionMode": "Flex",
                            "flexibleMode": "Flex",
                            "flexAttributes": {
                              "flexDirection": "row",
                              "alignItems": "center",
                              "justifyContent": "space-between"
                            }
                          },
                          "styleConfig": {
                            "widthSpec": {
                              "sizing": "FILL",
                              "value": 375
                            },
                            "heightSpec": {
                              "sizing": "FIXED",
                              "value": 56
                            },
                            "paddingValues": [
                              16,
                              20,
                              16,
                              20
                            ]
                          },
                          "processingMeta": {
                            "detectionScore": 0.93
                          },
                          "childElements": [
                            {
                              "elementId": "title_001",
                              "elementName": "Title",
                              "elementType": "Text",
                              "layoutConfig": {
                                "positionMode": "Flex"
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIT_CONTENT",
                                  "value": 120
                                },
                                "heightSpec": {
                                  "sizing": "FIT_CONTENT",
                                  "value": 24
                                },
                                "textConfig": {
                                  "fontSize": 18,
                                  "fontStyle": "bold",
                                  "fontFamily": "SF Pro Text",
                                  "lineHeight": 24,
                                  "letterSpacing": 0,
                                  "textAlign": [
                                    "left"
                                  ]
                                },
                                "textColor": {
                                  "rgbValues": [
                                    17,
                                    17,
                                    17
                                  ],
                                  "hexCode": "#111111"
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.98
                              },
                              "contentData": {
                                "textValue": "Discover"
                              },
                              "childElements": []
                            },
                            {
                              "elementId": "avatar_001",
                              "elementName": "Avatar",
                              "elementType": "Image",
                              "layoutConfig": {
                                "positionMode": "Flex"
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 32
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 32
                                },
                                "borderConfig": {
                                  "borderRadius": [
                                    16,
                                    16,
                                    16,
                                    16
                                  ]
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.91
                              },
                              "contentData": {
                                "imageSource": "https://cdn.codia.ai/i2d/avatar_001.png"
                              },
                              "childElements": []
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/pdf_to_design": {
      "post": {
        "operationId": "pdfToDesignV2",
        "summary": "Convert PDF to Design",
        "description": "**Cost**: 13 credits per processed page.\n\nExtract a structured, editable design tree out of one or more pages of a PDF. Each page becomes a fully-populated **`VisualElement`** tree — the same node shape as `image_to_design` — augmented with the vector / typography / transform fields that only PDFs carry.\n\n## What this API is for\n\nPDFs are vector documents: every paragraph, every shape, every gradient is stored as math, not pixels. This endpoint preserves that math instead of rasterizing it, so the tree you get back can be re-rendered, re-flowed, re-styled, or translated into another format without quality loss.\n\nTypical use cases:\n- **PDF → editable design**: open up an exported brochure / spec sheet / one-pager and turn it back into a Figma-like tree.\n- **PDF → PPT / HTML / code**: walk the tree to emit slides, web pages, or component code. (Our own `pdf_to_ppt` builds on this.)\n- **Content extraction**: pull every text run with its true font, size, and color, plus every vector shape with its actual path data.\n- **Asset salvage**: re-export embedded images and vector logos at original resolution.\n\nDifferences vs. `image_to_design`: input is a **PDF URL or PDF file**, output is **one tree per requested page** (not a single tree), and the per-node fields tagged \"PDF only\" in the shared schema **are** populated here (vector paths, character-level styling, transform matrices, etc.).\n\n## Sending the request\n\nThis endpoint supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with `pdf_url` and `page_no` when the PDF is already publicly reachable.\n2. **Direct upload mode**: send `multipart/form-data` with a local PDF file in the `pdf_file` field and one or more `page_no` fields.\n\nJSON example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/pdf_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -H 'Content-Type: application/json' \\\n  --data '{ \"pdf_url\": \"https://example.com/file.pdf\", \"page_no\": [0, 2, 5] }'\n```\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/pdf_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'pdf_file=@./file.pdf' \\\n  -F 'page_no=0' \\\n  -F 'page_no=2'\n```\n\n- **`pdf_url`** *(JSON mode, required)* — public URL of the PDF. Encrypted / password-protected PDFs are rejected.\n- **`pdf_file`** *(upload mode, required, binary)* — the PDF itself. Stream the file directly; do not base64-encode it.\n- **`page_no`** *(required)* — **0-indexed** page numbers. JSON mode uses an integer array; multipart mode repeats the `page_no` field once per page. Page count and file-size caps follow the global Codia API limits.\n\n## Response shape\n\nStandard Codia envelope; on success `data` carries:\n\n| Field | What it is | How to use it |\n|---|---|---|\n| `data.configuration` | Same `Configuration` object as `image_to_design` — `baseWidth`, `measurementUnit` (`px` \\| `pt`), `scalingFactor`. | Apply `scalingFactor` to every numeric value when rendering. `baseWidth` is the canvas width the trees were measured against (often page width in points or px, e.g. 1940). |\n| `data.size` | Page dimensions in document units: `{ width, height }`. | Use to set up artboard / viewport per page. All pages in a single PDF share this size. |\n| `data.pages[]` | One entry per requested page. Each entry is `{ visualElement: <Body root> }`. | Iterate pages in order; recurse into each `visualElement.childElements` to reach every region. |\n\n## Walking a page tree\n\nEach `data.pages[i].visualElement` is a `Body`-typed root with the same `VisualElement` shape used elsewhere in this API (see `image_to_design` for the full field guide). The headline pieces:\n\n- **`elementType`** — `Body` at the root, then `Layer` / `Group` / `Text` / `Image` / `Vector` / `Component` inside. PDFs use `Vector` heavily for shapes, gradients, and logos.\n- **`childElements`** — nested nodes; recurse depth-first.\n- **`layoutConfig` / `styleConfig` / `processingMeta`** — same semantics as `image_to_design`.\n- **`contentData`** — for `Text` reads `textValue`; for `Image` reads `imageSource` and/or the base64 in `imageData`; for `Vector` reads `vectorData` (see below).\n\n### PDF-specific fields you can actually use\n\nFields the shared schema tags \"PDF only\" — these are real, populated, and useful on this endpoint:\n\n- **`boundingBox`** *(`[x, y, width, height]`)* — exact node geometry in document coordinates. The most reliable positioning source for PDFs.\n- **`displayOrder`** — z-order within the page; lower = drawn first.\n- **`styleConfig.transformMatrix` / `extendedMatrix`** — affine transform applied to the node. `transformMatrix` is the 6-element PDF form `[a b c d e f]`; `extendedMatrix` is the CSS-compatible string. Apply this to handle rotated text, sheared images, etc.\n- **`styleConfig.characterData`** — per-character styling keyed by character index. Use this when a single text node mixes fonts, sizes, or colors (very common in PDFs).\n- **`styleConfig.cornerRadius`** — scalar fallback when the four-corner array isn't applicable.\n- **`styleConfig.isRightToLeft`** — direction flag for Arabic / Hebrew text.\n- **`contentData.vectorData`** — the actual vector path. Contains `pathItems[]` (each item is `{ pathType: \"l\" | \"c\", coordinates[] }` — `l` = line, `c` = cubic curve), plus `fillColor`, `strokeColor`, `strokeWidth`, `fillOpacity`, `strokeOpacity`, `lineCapStyle`, `lineJoinStyle`, `dashPattern`, `evenOddRule`, `isClosedPath`. Emit SVG `<path>` directly, or rebuild in your own renderer.\n- **`contentData.imageData`** — base64-encoded image bytes for embedded raster images (alongside or instead of `imageSource`).\n- **`contentData.svgTemplate`** — pre-rendered SVG markup template for compound vector content.\n- **`contentData.maskingMode` / `blendingType`** — PDF graphics-state knobs (clip / soft-mask, blend mode). Map to CSS `mix-blend-mode` / SVG `<mask>` as appropriate.\n\n### A minimal traversal\n\n```ts\nfor (const page of response.data.pages) {\n  console.log('page', page.visualElement.elementName, response.data.size);\n  walk(page.visualElement);\n}\n\nfunction walk(node, depth = 0) {\n  const pad = '  '.repeat(depth);\n  if (node.elementType === 'Text') console.log(pad, 'TXT', node.contentData?.textValue);\n  if (node.elementType === 'Image') console.log(pad, 'IMG', node.contentData?.imageSource);\n  if (node.elementType === 'Vector') console.log(pad, 'VEC', node.contentData?.vectorData?.pathItems?.length, 'segs');\n  node.childElements?.forEach(c => walk(c, depth + 1));\n}\n```\n\n## Inputs, limits, billing\n\n- **Input** — either JSON with `pdf_url` or `multipart/form-data` with `pdf_file`. Encrypted / password-protected PDFs are rejected.\n- **Page selection** — `page_no` uses **0-based** indices. JSON mode sends an integer array; multipart mode repeats the `page_no` field once per page. Out-of-range indices are silently dropped from the response.\n- **Billing** — consumes the unified Codia Open API balance (`GET /v2/open/credits`). 13 credits per processed page.\n- **Latency** — synchronous. Throughput is per-page; a long document may take many seconds. For very long PDFs, batch the request by `page_no` ranges and parallelize.\n- **Determinism** — same PDF + same `page_no` yields the same structural tree, but `elementId`s are regenerated each call; don't rely on cross-call ID stability.\n\n## Errors\n\nUses the standard envelope with non-zero `code` and a descriptive `message`. HTTP status codes:\n- `400` — bad input (missing `pdf_url` / `pdf_file`, malformed `page_no`, corrupt / encrypted PDF).\n- `401` / `403` — missing / wrong / unauthorized API key.\n- `402` — credits exhausted (see `/v2/open/auto_recharge`).\n- `429` — rate-limited; back off and retry.\n- `500` — internal error; safe to retry once.\n\n### `measurementUnit`\n\n`pdf_to_design` returns whichever unit the source PDF used. Most editorial / business PDFs report `pt` (PostScript points: 72 = 1 inch). PDFs exported from design tools at a specific pixel target may report `px`. Read this field per response and convert if your downstream renderer assumes one unit.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — fine for short PDFs. For documents with more than ~5 pages, prefer `POST /v2/open/tasks` with `operation=pdf_to_design` to avoid HTTP client timeouts, get webhook delivery, and support cancellation. Inputs and credit cost are identical to this v2 endpoint.",
        "tags": [
          "Design Conversion"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pdf_url",
                  "page_no"
                ],
                "properties": {
                  "pdf_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the PDF to process. Must be directly fetchable by Codia."
                  },
                  "page_no": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "description": "0-indexed page numbers to process, e.g. [0, 2, 5]."
                  }
                }
              },
              "example": {
                "pdf_url": "https://example.com/file.pdf",
                "page_no": [
                  0,
                  2,
                  5
                ]
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "pdf_file",
                  "page_no"
                ],
                "properties": {
                  "pdf_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The PDF file to process. Sent as a multipart binary part."
                  },
                  "page_no": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "description": "Repeat the multipart page_no field once per 0-indexed page to process."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful conversion. `data.pages` is an array — one entry per processed page, each containing a `visualElement` tree with the same `VisualElement` shape as `image_to_design` (plus the PDF-only fields documented in the operation description). `data.size` carries the page dimensions and `data.configuration` carries the canvas scale / unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfToDesignResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "configuration": {
                      "scalingFactor": 1,
                      "baseWidth": 1940,
                      "measurementUnit": "px"
                    },
                    "size": {
                      "height": 1080,
                      "width": 1940
                    },
                    "pages": [
                      {
                        "visualElement": {
                          "elementId": "page_001",
                          "elementName": "Page 1",
                          "elementType": "Body",
                          "boundingBox": [
                            0,
                            0,
                            1940,
                            1080
                          ],
                          "displayOrder": 0,
                          "layoutConfig": {
                            "positionMode": "Normal"
                          },
                          "styleConfig": {
                            "widthSpec": {
                              "sizing": "FIXED",
                              "value": 1940
                            },
                            "heightSpec": {
                              "sizing": "FIXED",
                              "value": 1080
                            }
                          },
                          "processingMeta": {
                            "detectionScore": 1
                          },
                          "childElements": [
                            {
                              "elementId": "txt_h1",
                              "elementName": "Heading",
                              "elementType": "Text",
                              "boundingBox": [
                                120,
                                96,
                                1040,
                                64
                              ],
                              "displayOrder": 1,
                              "layoutConfig": {
                                "positionMode": "Absolute",
                                "absoluteAttrs": {
                                  "coord": {
                                    "x": 120,
                                    "y": 96
                                  }
                                }
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 1040
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 64
                                },
                                "textConfig": {
                                  "fontSize": 48,
                                  "fontStyle": "bold",
                                  "fontFamily": "Inter",
                                  "lineHeight": 56,
                                  "letterSpacing": 0,
                                  "textAlign": [
                                    "left"
                                  ]
                                },
                                "textColor": {
                                  "rgbValues": [
                                    17,
                                    17,
                                    17
                                  ],
                                  "hexCode": "#111111"
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.99
                              },
                              "contentData": {
                                "textValue": "Q4 2025 Review"
                              },
                              "childElements": []
                            },
                            {
                              "elementId": "vec_chart",
                              "elementName": "Pie slice",
                              "elementType": "Vector",
                              "boundingBox": [
                                120,
                                240,
                                320,
                                320
                              ],
                              "displayOrder": 2,
                              "layoutConfig": {
                                "positionMode": "Absolute",
                                "absoluteAttrs": {
                                  "coord": {
                                    "x": 120,
                                    "y": 240
                                  }
                                }
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 320
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 320
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.95
                              },
                              "contentData": {
                                "vectorData": {
                                  "pathItems": [
                                    {
                                      "pathType": "l",
                                      "coordinates": [
                                        {
                                          "xPosition": 160,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 160,
                                          "yPosition": 160
                                        }
                                      ]
                                    },
                                    {
                                      "pathType": "c",
                                      "coordinates": [
                                        {
                                          "xPosition": 160,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 247,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 320,
                                          "yPosition": 73
                                        },
                                        {
                                          "xPosition": 320,
                                          "yPosition": 160
                                        }
                                      ]
                                    }
                                  ],
                                  "fillColor": {
                                    "rgbValues": [
                                      34,
                                      197,
                                      94
                                    ],
                                    "hexCode": "#22C55E"
                                  },
                                  "fillOpacity": 1,
                                  "strokeWidth": 0,
                                  "isClosedPath": true
                                }
                              },
                              "childElements": []
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/remove_bg": {
      "post": {
        "operationId": "removeBackgroundV2",
        "summary": "Remove Background",
        "description": "**Cost**: 13 credits per successful call.\n\nRemove Background supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v2/open/remove_bg' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./product.png'\n```\n\nCut out the foreground subject of an image and return a PNG with a fully transparent background. A synchronous, single-purpose endpoint — no model parameter, no prompt, no options.\n\n## What this API is for\n\nProduct photos for storefronts, profile avatars, sticker generation, compositing pipelines — any time you need the subject without its scene. Returns a true alpha-channel PNG (not a flat white background).\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the image to process. PNG, JPG, or WebP. Must be reachable from Codia's network. |\n\n## Response\n\nStandard envelope. Unique to this endpoint (not the shared `ImageUrlListSuccess`): `data.image_url` is a **single** string — a CDN URL to the processed transparent PNG. Download promptly; the URL is pre-signed.\n\n```json\n{ \"code\": 0, \"message\": \"ok\", \"data\": { \"image_url\": \"https://cdn.codia.ai/bg-removed/abc.png\" } }\n```\n\n## Billing & limits\n\n- 13 credits per successful call, from the unified Codia Open API balance.\n- Synchronous; typically completes within a few seconds.\n- Output is always PNG with an alpha channel, at the input image's resolution.\n\n## Errors\n\nStandard set — `400` for bad input, `401`/`403` auth, `402` credits, `429` rate limit, `500` internal.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=remove_bg` instead. Inputs and credit cost are identical to this v2 endpoint.",
        "tags": [
          "Image Processing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "image_url"
                ],
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the image to process"
                  }
                }
              },
              "example": {
                "image_url": "https://example.com/product.jpg"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful background removal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveBgResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "image_url": "https://processed-image-url.com/result.png"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 409,
                  "message": "Idempotency-Key reused with different request body",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional user-supplied key (up to 255 chars). When set, a successful (2xx) response is cached for 7 days. A retry with the same key + identical request body returns the cached response (no extra credit charged); a retry with the same key but a different body returns 409. Use to make POST safe to retry on network failures."
          }
        ]
      }
    },
    "/v2/open/pdf_to_ppt": {
      "post": {
        "operationId": "pdfToPptAlias",
        "summary": "Create PDF to PPT Task",
        "description": "Compatibility alias for creating a `pdf_to_ppt` task. Prefer `POST /v2/open/tasks` with `operation: \"pdf_to_ppt\"` for new integrations. **Cost** comes from `products.open_api.tools.pdf_to_ppt` in `pricing.yaml` multiplied by processed page count.",
        "tags": [
          "Tasks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenAPITaskCreateRequest"
              },
              "examples": {
                "direct": {
                  "value": {
                    "pdf_url": "https://example.com/deck.pdf",
                    "page_no": [
                      0
                    ],
                    "title": "Deck title"
                  }
                },
                "task_envelope": {
                  "value": {
                    "operation": "pdf_to_ppt",
                    "input": {
                      "pdf_url": "https://example.com/deck.pdf",
                      "page_no": [
                        0
                      ],
                      "title": "Deck title"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Task receipt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/tasks/{task_id}/reconvert": {
      "post": {
        "operationId": "reconvertOpenApiTask",
        "summary": "Reconvert Task Pages",
        "description": "Re-run conversion for selected pages of a succeeded Open API task. This is intended for task outputs that can be improved page-by-page, such as `pdf_to_ppt` image-only pages.",
        "tags": [
          "Tasks"
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "page_no": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated task state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskGetResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/credits": {
      "get": {
        "operationId": "getOpenApiCredits",
        "summary": "Get API Credit Balance",
        "tags": [
          "Account"
        ],
        "description": "**Cost**: Free — account query.\n\nRead the **live** Codia Open API balance for the authenticated account. The balance is shared across every Open API endpoint (image_to_design, pdf_to_*, svg_converter, image/*, etc.) — there is no per-capability quota.\n\nCall this before submitting expensive jobs (e.g. PDF-to-PPT, multi-image generation) to fail fast on insufficient funds rather than getting a `402` mid-flow. Pair with `/v2/open/auto_recharge` if you want to top up automatically.\n\n## Response shape\n\nStandard envelope. `data.credits` is an integer — current balance in credits. There is no fractional unit.\n\n## Errors\n\n- `401` — missing / invalid API key.\n- `500` — internal error; retry safe.",
        "responses": {
          "200": {
            "description": "Credit balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditsResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "product": "open_api",
                    "available_credits": 4280,
                    "monthly_credits_remaining": 4000,
                    "topup_credits_remaining": 280,
                    "used_credits": 720,
                    "total_credits": 5000,
                    "current_period_start": "2026-05-01T00:00:00Z",
                    "current_period_end": "2026-06-01T00:00:00Z",
                    "next_reset_at": "2026-06-01T00:00:00Z",
                    "auto_recharge_enabled": true,
                    "low_balance_threshold": 200,
                    "auto_recharge_credits": 500,
                    "monthly_max_recharge_credits": 5000
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v2/open/limits": {
      "get": {
        "operationId": "getOpenApiLimits",
        "tags": [
          "Account"
        ],
        "summary": "Get API Limits and Prices",
        "description": "Returns the live Open API limits for the authenticated API key, including image caps, PDF caps, async task queue/concurrency limits, rate limits, and per-operation credit costs. This endpoint is free and does not consume credits.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Open API limits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPILimitsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/usage": {
      "get": {
        "operationId": "listOpenApiUsage",
        "summary": "List API Usage Records",
        "tags": [
          "Account"
        ],
        "description": "**Cost**: Free — account query.\n\nPaginated, **call-level** usage history for the unified Codia Open API balance. Each record represents one billable Open API call: which endpoint, when, how many credits, and (if applicable) which model.\n\nUse it for billing reconciliation, building usage dashboards, or surfacing per-feature consumption to your end users. The latest call appears first.\n\n## Query parameters\n\n| Param | Type | Default | Notes |\n|---|---|---|---|\n| `start_date` | `YYYY-MM-DD` | — | Lower bound (inclusive). Server time zone. Omit for no lower bound. |\n| `end_date` | `YYYY-MM-DD` | — | Upper bound (inclusive). Omit for no upper bound. |\n| `page` | integer | `1` | 1-indexed page number. |\n| `page_size` | integer | `50` | Records per page. Larger pages are slower; pick the smallest size your UI needs. |\n\n## Response shape\n\nStandard envelope. `data` carries `records[]` plus pagination metadata (`page`, `page_size`, `total`). Each `records[i]` minimally has: `endpoint`, `operation_id`, `credits`, `model` (when set), `created_at`. Empty range → empty `records` and `total: 0`, not an error.\n\n## Errors\n\n- `400` — malformed date (must be `YYYY-MM-DD`), or `end_date` before `start_date`.\n- `401` — auth issue.\n- `500` — internal error.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage records",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageListResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "items": [
                      {
                        "request_id": "550e8400-e29b-41d4-a716-446655440000",
                        "endpoint": "/v2/open/image_to_design",
                        "operation": "image_to_design",
                        "credits_used": 1,
                        "status": "success",
                        "created_at": "2026-05-28T08:23:11Z"
                      }
                    ],
                    "total": 137,
                    "page": 1,
                    "page_size": 50,
                    "has_more": true
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v2/open/auto_recharge": {
      "get": {
        "operationId": "getAutoRecharge",
        "summary": "Get Auto Recharge Settings",
        "tags": [
          "Account"
        ],
        "description": "**Cost**: Free — account query.\n\nRead the current auto-recharge configuration for the authenticated account. Auto-recharge keeps long-running pipelines from breaking on `402` by topping up credits automatically when the balance drops below a threshold.\n\n## Response shape\n\nStandard envelope. `data` carries:\n\n| Field | Meaning |\n|---|---|\n| `enabled` | Whether auto-recharge is currently on. |\n| `threshold_credits` | When the balance falls **at or below** this number, a recharge fires. |\n| `recharge_credits` | How many credits each recharge adds. One of `100`, `200`, `500`. |\n| `monthly_max_recharge_credits` | Safety cap on total auto-recharge spend per calendar month. Once exhausted, recharges pause until the next month. |\n\nIf auto-recharge has never been configured, `enabled` is `false` and the numeric fields may be missing or zero.\n\n## Errors\n\n- `401` — auth issue.\n- `500` — internal error.",
        "responses": {
          "200": {
            "description": "Auto recharge settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoRechargeResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "enabled": true,
                    "threshold_credits": 200,
                    "recharge_credits": 200,
                    "monthly_max_recharge_credits": 5000
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "updateAutoRecharge",
        "summary": "Update Auto Recharge Settings",
        "tags": [
          "Account"
        ],
        "description": "**Cost**: Free — settings update (does not include the recharge itself).\n\nUpdate the auto-recharge configuration. Send only the fields you want to change — omitted fields keep their current values.\n\n## Body fields\n\n| Field | Type | Notes |\n|---|---|---|\n| `enabled` | boolean | Master switch. Set `false` to disable auto-recharge entirely (other fields keep their values but are inert). |\n| `threshold_credits` | integer | Auto-recharge fires when the live balance falls at or below this number. |\n| `recharge_credits` | integer | How many credits to add per recharge. Must be one of `100`, `200`, `500`. |\n| `monthly_max_recharge_credits` | integer | Hard cap on auto-recharge spend per calendar month — once exhausted, further auto-recharges pause until the month rolls over. Set this to bound your worst-case bill. |\n\nA recharge bills the payment method on file at the moment it fires; make sure the account has a valid card and budget for it.\n\n## Response\n\nStandard envelope. `data` mirrors the updated settings (same shape as the GET response).\n\n## Errors\n\n- `400` — invalid combination (e.g. `recharge_credits` not in the allowed enum).\n- `401` — auth issue.\n- `402` — no valid payment method on file.\n- `500` — internal error.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "threshold_credits": {
                    "type": "integer"
                  },
                  "recharge_credits": {
                    "type": "integer",
                    "enum": [
                      100,
                      200,
                      500
                    ]
                  },
                  "monthly_max_recharge_credits": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoRechargeResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "enabled": true,
                    "threshold_credits": 200,
                    "recharge_credits": 200,
                    "monthly_max_recharge_credits": 5000
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v2/open/tasks": {
      "post": {
        "operationId": "createOpenApiTask",
        "summary": "Create Open API Task",
        "description": "Create a Codia Open API asynchronous task. Use this endpoint for every async Open API operation, including PDF to editable PPT.\n\n## Supported operations\n\n| operation | input | credits |\n|---|---|---|\n| `image_to_design` | `{ \"image_url\": \"https://...\" }` | pricing config |\n| `pdf_to_design` | `{ \"pdf_url\": \"https://...\", \"page_no\": [0, 1] }` | pricing config per page |\n| `pdf_to_ppt` | `{ \"upload_id\": \"upl_...\", \"page_no\": [0, 1], \"title\": \"Deck title\" }` or `{ \"pdf_url\": \"https://your-storage/...\" }` | pricing config per page |\n| `remove_bg` | `{ \"image_url\": \"https://...\" }` | pricing config |\n| `image_generate` | `{ \"prompt\": \"...\", \"n\": 1, \"size\": \"1024x1024\", \"model\": \"...\" }` | pricing config per image |\n| `image_to_image` | `{ \"image_url\": \"https://...\", \"prompt\": \"...\", \"model\": \"...\", \"size\": \"1024x1024\", \"quality\": \"high\" }` | pricing config |\n| `image_remix` | `{ \"image_url\": \"https://...\", \"prompt\": \"...\", \"size\": \"1024x1024\" }` | pricing config |\n| `image_reframe` | `{ \"image_url\": \"https://...\", \"resolution\": \"1024x1024\" }` | pricing config |\n| `image_upscale` | `{ \"image_url\": \"https://...\", \"model\": \"...\" }` | pricing config |\n| `image_object_erase` | `{ \"image_url\": \"https://...\", \"mask_url\": \"https://...\" }` | pricing config |\n| `image_watermark_remove` | `{ \"image_url\": \"https://...\" }` | pricing config |\n| `image_layering` | `{ \"image_url\": \"https://...\" }` | pricing config |\n| `image_describe` | `{ \"image_url\": \"https://...\", \"model\": \"...\" }` | pricing config |\n| `image_replace_background` | `{ \"image_url\": \"https://...\", \"prompt\": \"...\", \"model\": \"...\" }` | pricing config |\n\nFor private or local PDF-to-PPT inputs, call `POST /v2/open/uploads` first and pass the returned opaque `upload_id`. Codia-managed uploads do not return public file URLs. Use `pdf_url` only for files hosted by your own storage.\n\n## Task ownership\n\nTasks are scoped to the API key that created them. Use the same API key to query, list, or cancel a task. Other API keys under the same Codia account do not see or control that task.\n\n## Idempotency\n\nSend `Idempotency-Key` on create requests when retries are possible. A repeated request with the same API key, operation, and idempotency key returns the existing task receipt instead of creating duplicate work. Reusing the same key with different `input` or `callback_url` returns HTTP 409. If two identical create requests race, only one task is kept and the duplicate request does not consume extra credits.\n\n## Callback\n\nOptionally pass `callback_url` at the top level. Codia sends a webhook when the task reaches a terminal state. The same URL may also be passed inside `input.callback_url` for compatibility, but the top-level field is preferred.",
        "tags": [
          "Tasks"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Send `Idempotency-Key` on create requests when retries are possible. A repeated request with the same API key, operation, and idempotency key returns the existing task receipt instead of creating duplicate work. Reusing the same key with different `input` or `callback_url` returns HTTP 409. If two identical create requests race, only one task is kept and the duplicate request does not consume extra credits."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenAPITaskCreateRequest"
              },
              "examples": {
                "pdf_to_ppt": {
                  "summary": "PDF to editable PPT",
                  "value": {
                    "operation": "pdf_to_ppt",
                    "input": {
                      "page_no": [
                        0,
                        1,
                        2
                      ],
                      "title": "Quarterly report",
                      "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000"
                    },
                    "callback_url": "https://your-server.com/webhook/codia-task"
                  }
                },
                "image_to_design": {
                  "summary": "Image to design DSL",
                  "value": {
                    "operation": "image_to_design",
                    "input": {
                      "image_url": "https://example.com/screenshot.png"
                    }
                  }
                },
                "pdf_to_design": {
                  "summary": "PDF to design DSL",
                  "value": {
                    "operation": "pdf_to_design",
                    "input": {
                      "pdf_url": "https://example.com/document.pdf",
                      "page_no": [
                        0,
                        1
                      ]
                    }
                  }
                },
                "remove_bg": {
                  "summary": "Remove image background",
                  "value": {
                    "operation": "remove_bg",
                    "input": {
                      "image_url": "https://example.com/product.png"
                    }
                  }
                },
                "image_generate": {
                  "summary": "Generate image",
                  "value": {
                    "operation": "image_generate",
                    "input": {
                      "prompt": "A clean product hero image on white background",
                      "n": 1,
                      "size": "1024x1024"
                    }
                  }
                },
                "image_object_erase": {
                  "summary": "Erase masked object",
                  "value": {
                    "operation": "image_object_erase",
                    "input": {
                      "image_url": "https://example.com/photo.png",
                      "mask_url": "https://example.com/mask.png"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing task receipt returned by idempotency replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskCreateResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "task_id": "550e8400-e29b-41d4-a716-446655440000",
                    "operation": "pdf_to_ppt",
                    "status": "pending",
                    "created_at": 1764000000
                  }
                }
              }
            }
          },
          "202": {
            "description": "Task accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskCreateResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "task_id": "550e8400-e29b-41d4-a716-446655440000",
                    "operation": "pdf_to_ppt",
                    "status": "processing",
                    "created_at": 1764000000
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Send `Idempotency-Key` on create requests when retries are possible. A repeated request with the same API key, operation, and idempotency key returns the existing task receipt instead of creating duplicate work. Reusing the same key with different `input` or `callback_url` returns HTTP 409. If two identical create requests race, only one task is kept and the duplicate request does not consume extra credits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "operationId": "listOpenApiTasks",
        "summary": "List Open API Tasks",
        "description": "List tasks owned by the current API key. When `status` is omitted, the API returns active tasks only: `pending` and `processing`.\n\nUse comma-separated filters, for example `operation=pdf_to_ppt&status=pending,processing&limit=20`. Results are ordered by `created_at` descending. When `has_more` is true, pass the oldest returned task's `created_at` value as `after` to fetch the next page.",
        "tags": [
          "Tasks"
        ],
        "parameters": [
          {
            "name": "operation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated operations to include, for example pdf_to_ppt or image_to_design,pdf_to_design."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "pending,processing"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "oneOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string",
                  "format": "date-time"
                }
              ]
            },
            "description": "Pagination cursor. Pass the oldest returned created_at timestamp, or an RFC3339 timestamp."
          }
        ],
        "responses": {
          "200": {
            "description": "Task list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskListResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "tasks": [
                      {
                        "task_id": "550e8400-e29b-41d4-a716-446655440000",
                        "operation": "pdf_to_ppt",
                        "status": "succeeded",
                        "progress": 100,
                        "result": {
                          "ppt_url": "https://cdn.codia.ai/pptx/q4-report.pptx",
                          "note_slide_id": "note_slide_123"
                        },
                        "created_at": 1764000000,
                        "updated_at": 1764000088,
                        "started_at": 1764000005,
                        "completed_at": 1764000088
                      }
                    ],
                    "has_more": false
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/tasks/{task_id}": {
      "get": {
        "operationId": "getOpenApiTask",
        "summary": "Get Open API Task",
        "description": "**Cost**: Free — task status query.\n\nGet the latest state for one Open API task. The request must use the same API key that created the task. Terminal states are `succeeded`, `failed`, and `canceled`.\n\nFor `pdf_to_ppt`, the successful result contains `result.ppt_url` and may contain `result.note_slide_id`. For `image_to_design` and `pdf_to_design`, `result` contains the same design data shape returned by the existing synchronous endpoints.",
        "tags": [
          "Tasks"
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Task ID returned by POST /v2/open/tasks."
          }
        ],
        "responses": {
          "200": {
            "description": "Task status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskGetResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "task_id": "550e8400-e29b-41d4-a716-446655440000",
                    "operation": "pdf_to_ppt",
                    "status": "succeeded",
                    "progress": 100,
                    "result": {
                      "ppt_url": "https://cdn.codia.ai/pptx/q4-report.pptx",
                      "note_slide_id": "note_slide_123"
                    },
                    "created_at": 1764000000,
                    "updated_at": 1764000088,
                    "started_at": 1764000005,
                    "completed_at": 1764000088
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "code": 404,
                  "message": "task not found",
                  "data": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/tasks/{task_id}/cancel": {
      "post": {
        "operationId": "cancelOpenApiTask",
        "summary": "Cancel Open API Task",
        "description": "**Cost**: Free — cancellation refunds unused reserved credits.\n\nCancel a task that is still `pending` or `processing`. The request must use the same API key that created the task. Canceling marks the task `canceled` immediately, refunds unused reserved credits idempotently, and asks the worker to stop when it is running in the current process.\n\nFor `pdf_to_ppt`, credits are charged per converted page. If a 15-page task is canceled after 10 pages have already finished conversion, the cancel call refunds 65 credits and the task remains charged for 130 credits. If the task is still pending or has not converted any pages yet, all reserved credits are refunded. Non-page-based tasks refund their full reserved amount when canceled before completion.\n\nThe response includes `credits_reserved`, `credits_charged`, and `credits_refunded` so clients can show the final billing outcome.\n\nA `succeeded` task returns HTTP 409 because completed work cannot be canceled. `failed` and `canceled` tasks return 200 with their current state.",
        "tags": [
          "Tasks"
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Task ID returned by POST /v2/open/tasks."
          }
        ],
        "responses": {
          "200": {
            "description": "Task canceled or already terminal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPITaskCancelResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "task_id": "550e8400-e29b-41d4-a716-446655440000",
                    "operation": "pdf_to_ppt",
                    "status": "canceled"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Task already succeeded and cannot be canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/uploads": {
      "post": {
        "operationId": "uploadOpenApiFile",
        "summary": "Upload Open API File",
        "description": "Upload a local PDF for a follow-up Open API task. This endpoint returns an opaque `upload_id`, not a public file URL, so it cannot be used as free public S3 hosting. Pass the `upload_id` to `POST /v2/open/tasks` as `input.upload_id` for `pdf_to_ppt`. The upload is scoped to the same API key and expires after a short retention window.",
        "tags": [
          "Tasks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF file to upload. The canonical field name is `file`."
                  },
                  "pdf_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatibility field name accepted for PDF uploads."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPIUploadResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000",
                    "filename": "report.pdf",
                    "size": 1234567,
                    "content_type": "application/pdf",
                    "expires_at": 1764086400
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v2/open/estimate": {
      "post": {
        "operationId": "estimateOpenApiTask",
        "summary": "Estimate Open API Task Credits",
        "description": "Estimate task credits before creating a task. For `pdf_to_ppt`, pass either `input.upload_id` from `/v2/open/uploads` or a caller-hosted `input.pdf_url`. Estimation does not create a task and does not deduct credits.",
        "tags": [
          "Tasks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenAPIEstimateRequest"
              },
              "examples": {
                "pdf_to_ppt_upload": {
                  "summary": "Estimate uploaded PDF to PPT",
                  "value": {
                    "operation": "pdf_to_ppt",
                    "input": {
                      "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000",
                      "page_no": [
                        0,
                        1,
                        2
                      ]
                    }
                  }
                },
                "pdf_to_ppt_url": {
                  "summary": "Estimate caller-hosted PDF to PPT",
                  "value": {
                    "operation": "pdf_to_ppt",
                    "input": {
                      "pdf_url": "https://example.com/report.pdf",
                      "page_no": [
                        0,
                        1,
                        2
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Credit estimate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPIEstimateResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "operation": "pdf_to_ppt",
                    "credits": 39,
                    "page_count": 3,
                    "unit": "13 credits per page",
                    "available_credits": 120,
                    "sufficient": true
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/open/image_to_design": {
      "post": {
        "operationId": "imageToDesignV1",
        "summary": "[Deprecated] Convert Image to Design",
        "description": "**⚠️ Deprecated — legacy v1 endpoint.** This path is retained only for existing integrations. New integrations should use `POST /v2/open/image_to_design` (v2 unified Open API). Input, output, and billing are unchanged — this endpoint keeps its own independent legacy credit SKU.\n\n---\n\n**Cost**: 13 credits per successful call.\n\nTurn a single UI screenshot into an editable, hierarchical design tree — the same representation Codia Studio uses internally and the foundation that downstream code-generation, Figma-import, and layer-decomposition workflows are built on.\n\n## Request options\n\nThis endpoint supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with `image_url` when the image is already publicly reachable.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nJSON example:\n\n```bash\ncurl 'https://api.codia.ai/v1/open/image_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -H 'Content-Type: application/json' \\\n  --data '{ \"image_url\": \"https://example.com/screenshot.png\" }'\n```\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v1/open/image_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./screenshot.png'\n```\n\n## What this API is for\n\nGiven a public image URL of a UI (mobile screen, desktop page, dashboard, marketing hero — anything pixel-based), the service runs detection + layout reconstruction and returns a tree of **`VisualElement`** nodes that mirror how a designer would have built the screen in Figma or Sketch:\n\n- Every visible region becomes a node with a position, a size, and a style.\n- Container/child relationships are preserved (e.g. a card holds its title, body, and CTA).\n- Layout intent (Flex vs. Absolute), text typography (font, size, weight, color), shapes, images, and effects are all reified into structured fields — not flattened into a single bitmap.\n\nTypical use cases:\n- **Image → code / image → Figma**: walk the tree and emit React / HTML / Figma nodes.\n- **Design QA**: diff a screenshot against a spec by comparing structured trees.\n- **Layer extraction**: feed the tree into downstream tools (e.g. our PDF/PPT pipeline or your own renderer).\n- **Agent / LLM workflows**: give a model a structured layout instead of raw pixels so it can reason about \"the price label sits inside the product card.\"\n\n## How the response is shaped\n\nThe response uses the standard Codia envelope (`code`, `message`, `data`). On success `code = 0` and `data` contains two fields:\n\n| Field | What it is | How to use it |\n|---|---|---|\n| `data.configuration` | Global rendering settings — `baseWidth`, `measurementUnit` (`px` \\| `pt`), `scalingFactor`. | Multiply every numeric value in the tree by `scalingFactor` when rendering. Treat `baseWidth` as the canvas width the tree was measured against (375 for mobile, 1440 for desktop, etc.). |\n| `data.visualElement` | The **root** of the visual tree. Always an `elementType: \"Body\"` node whose `childElements` recursively contain the rest of the page. | Traverse depth-first. The fields you care about per node depend on `elementType` (see below). |\n\n## Walking the `VisualElement` tree\n\nEach node has the same shape, but a handful of fields are populated based on `elementType`:\n\n| `elementType` | Populated content fields | Typical meaning |\n|---|---|---|\n| `Body` | `layoutConfig`, `styleConfig` | Root of the page / artboard |\n| `Layer` | `layoutConfig`, `styleConfig` | Generic container (card, section) |\n| `Group` | `childElements` | Pure grouping, no own visuals |\n| `Text` | `styleConfig.textConfig`, `contentData.textValue` | A run of text |\n| `Image` | `contentData.imageSource` | Raster image (URL) |\n| `Vector` | `contentData.vectorData` | Vector shape (mostly PDF; image_to_design uses `Image` / `Layer` for shapes) |\n| `Component` | `componentSpec`, `contentData.componentReference` | Reusable detected component |\n\nFor every node:\n- **`elementId` / `elementName`** — stable identifier + human label. Use `elementId` as your React `key` or Figma node id.\n- **`childElements`** — array of nested `VisualElement`s. Empty leaf for text/image. Recurse here.\n- **`processingMeta.detectionScore`** — confidence in `[0, 1]`. Filter or warn on low-confidence nodes when generating production code.\n- **`processingMeta.surfaceArea`** — pixel area; useful for sorting or pruning tiny artifacts.\n\n### `layoutConfig` — where the node sits\n\n- `positionMode`:\n  - `\"Flex\"` — the node participates in its parent's flex layout. Read `flexAttributes` (`flexDirection`, `alignItems`, `justifyContent`, `flexWrap`) on the **parent** to know how children flow.\n  - `\"Absolute\"` — positioned at exact coordinates. Read `absoluteAttrs.coord` for the offset relative to the parent.\n  - `\"Normal\"` / `\"Relative\"` — document-flow positioning (rare on this endpoint; common at the root).\n- `flexibleMode` — present on containers, identifies the flex variant the detector chose.\n\nIn practice, the root `Body` is `Flex` and most children inherit Flex from their parent, so a naive emitter that maps `positionMode` → CSS `display: flex` + `position: absolute` will reproduce the layout faithfully.\n\n### `styleConfig` — what the node looks like\n\nThis is the largest object in the response. Group the fields by what they describe:\n\n- **Size** — `widthSpec`, `heightSpec`. Each is a `DimensionSpec` with `sizing` (`FIXED` = exact px, `FILL` = stretch to parent, `FIT_CONTENT` = shrink to children) and a numeric `value`. Map `FIXED → width: Npx`, `FILL → flex: 1` (or `width: 100%`), `FIT_CONTENT → width: auto`.\n- **Text** (on `Text` nodes) — `textConfig.{fontSize, fontStyle, fontFamily, lineHeight, letterSpacing, textAlign}` + `textColor` (a `VisualColor` with `rgbValues` and `hexCode`). `letterSpacing` is in CSS px against the detected font-size; Studio converts to a percentage at import time (Figma semantics).\n- **Border** — `borderConfig.{borderWidth, borderStyle, borderColor, borderRadius}`. `borderRadius` is an array `[tl, tr, br, bl]`; collapse to a single value if all four are equal.\n- **Background** — `backgroundConfig` is a `oneOf` with three shapes discriminated by `type`:\n  - `\"COLOR\"` → solid fill via `backgroundColor`.\n  - `\"IMAGE\"` → `imageUrl` + CSS-style `backgroundSize` / `backgroundPosition`.\n  - `\"LINEAR_GRADIENT\"` → `deg` + `gradientStops[]` (each stop = `{color, position}` where `position` is 0–1).\n- **Effects / transforms** — `effectsList` (shadows, blurs), `opacityLevel` (0–255 — divide by 255 for CSS), `rotationAngle` (degrees), `paddingValues`, `overflowMode`.\n\nFields tagged \"PDF only\" in the schema (`characterData`, `transformMatrix`, `cornerRadius`, `isRightToLeft`, `extendedMatrix`) are not produced by image_to_design — they're shared with `pdf_to_design` and you can ignore them here. Same for `boundingBox`, `displayOrder`, and `contentData.{svgTemplate, imageData, maskingMode, blendingType}`.\n\n### `contentData` — what the node displays\n\nOnly populated on leaf-ish nodes:\n- `textValue` — the actual string for `Text` nodes.\n- `imageSource` — public URL for `Image` nodes (re-hosted on Codia's CDN; safe to embed).\n- `componentReference` / `componentAttributes` — pointer + props for detected components, paired with `componentSpec` on the same node.\n\n## A minimal traversal\n\n```ts\nfunction walk(node: VisualElement, depth = 0) {\n  console.log('  '.repeat(depth), node.elementType, node.elementId,\n    node.elementType === 'Text' ? node.contentData?.textValue :\n    node.elementType === 'Image' ? node.contentData?.imageSource : '');\n  node.childElements?.forEach(child => walk(child, depth + 1));\n}\nwalk(response.data.visualElement);\n```\n\n## Inputs, limits, billing\n\n- **Input** — either JSON with a single `image_url` pointing to a publicly fetchable PNG / JPG / WebP, or `multipart/form-data` with an `image` file. For direct upload, Codia checks credits before reading and uploading the file. Field name `file` is also accepted for compatibility. Maximum side length and file-size limits follow the global Codia API limits.\n- **Billing** — consumes the unified Codia Open API balance (see `GET /v2/open/credits`). 13 credits per successful call.\n- **Latency** — synchronous; typical end-to-end is single-digit seconds for mobile screens, longer for dense desktop pages.\n- **Idempotency** — same image URL produces the same tree modulo `elementId` regeneration; do not rely on stable IDs across calls.\n\n## Errors\n\nUses the standard envelope with non-zero `code` and a descriptive `message`. HTTP status codes:\n- `400` — bad input (missing/invalid `image_url`, unreachable URL, unsupported MIME).\n- `401` / `403` — missing / wrong / unauthorized API key.\n- `402` — credit balance exhausted (top up via the dashboard or `/v2/open/auto_recharge`).\n- `429` — rate-limited; back off and retry.\n- `500` — internal error; safe to retry once.\n\n### `measurementUnit`\n\nAlways `px` for `image_to_design` — screen captures and rasterised UI are inherently pixel-based.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=image_to_design` instead. Inputs and credit cost are identical to this endpoint.",
        "tags": [
          "Legacy (v1)"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "image_url"
                ],
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the image to analyze. Must be a directly fetchable PNG, JPG, or WebP — pre-signed S3 or CDN URLs are fine, but pages that gate the asset behind login or JavaScript will fail. There is no separate `image_base64` field; host the image first."
                  }
                }
              },
              "example": {
                "image_url": "https://example.com/screenshot.png"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Local image file to analyze. Use this when the image is not already hosted. The server checks credits before reading and uploading the file. Field name `file` is also accepted for compatibility."
                  }
                }
              },
              "encoding": {
                "image": {
                  "contentType": "image/png, image/jpeg, image/webp, image/gif"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful conversion. `data.visualElement` is the root `Body` node of the tree — recurse through `childElements` to reach every detected region. `data.configuration` carries the canvas base width, unit, and a scale factor you should multiply every numeric value by before rendering. See the operation description above for a full walkthrough of `elementType`, `layoutConfig`, `styleConfig`, and `contentData`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageToDesignResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "configuration": {
                      "baseWidth": 375,
                      "measurementUnit": "px",
                      "scalingFactor": 1
                    },
                    "visualElement": {
                      "elementId": "root_001",
                      "elementName": "Page",
                      "elementType": "Body",
                      "layoutConfig": {
                        "positionMode": "Normal",
                        "flexibleMode": "Flex",
                        "flexAttributes": {
                          "flexDirection": "column",
                          "alignItems": "stretch",
                          "justifyContent": "flex-start"
                        }
                      },
                      "styleConfig": {
                        "widthSpec": {
                          "sizing": "FIXED",
                          "value": 375
                        },
                        "heightSpec": {
                          "sizing": "FIXED",
                          "value": 812
                        },
                        "backgroundConfig": {
                          "type": "COLOR",
                          "backgroundColor": {
                            "rgbValues": [
                              255,
                              255,
                              255
                            ],
                            "hexCode": "#FFFFFF"
                          }
                        }
                      },
                      "processingMeta": {
                        "surfaceArea": 304500,
                        "detectionScore": 0.96
                      },
                      "childElements": [
                        {
                          "elementId": "header_001",
                          "elementName": "Header",
                          "elementType": "Layer",
                          "layoutConfig": {
                            "positionMode": "Flex",
                            "flexibleMode": "Flex",
                            "flexAttributes": {
                              "flexDirection": "row",
                              "alignItems": "center",
                              "justifyContent": "space-between"
                            }
                          },
                          "styleConfig": {
                            "widthSpec": {
                              "sizing": "FILL",
                              "value": 375
                            },
                            "heightSpec": {
                              "sizing": "FIXED",
                              "value": 56
                            },
                            "paddingValues": [
                              16,
                              20,
                              16,
                              20
                            ]
                          },
                          "processingMeta": {
                            "detectionScore": 0.93
                          },
                          "childElements": [
                            {
                              "elementId": "title_001",
                              "elementName": "Title",
                              "elementType": "Text",
                              "layoutConfig": {
                                "positionMode": "Flex"
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIT_CONTENT",
                                  "value": 120
                                },
                                "heightSpec": {
                                  "sizing": "FIT_CONTENT",
                                  "value": 24
                                },
                                "textConfig": {
                                  "fontSize": 18,
                                  "fontStyle": "bold",
                                  "fontFamily": "SF Pro Text",
                                  "lineHeight": 24,
                                  "letterSpacing": 0,
                                  "textAlign": [
                                    "left"
                                  ]
                                },
                                "textColor": {
                                  "rgbValues": [
                                    17,
                                    17,
                                    17
                                  ],
                                  "hexCode": "#111111"
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.98
                              },
                              "contentData": {
                                "textValue": "Discover"
                              },
                              "childElements": []
                            },
                            {
                              "elementId": "avatar_001",
                              "elementName": "Avatar",
                              "elementType": "Image",
                              "layoutConfig": {
                                "positionMode": "Flex"
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 32
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 32
                                },
                                "borderConfig": {
                                  "borderRadius": [
                                    16,
                                    16,
                                    16,
                                    16
                                  ]
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.91
                              },
                              "contentData": {
                                "imageSource": "https://cdn.codia.ai/i2d/avatar_001.png"
                              },
                              "childElements": []
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "deprecated": true
      }
    },
    "/v1/open/pdf_to_design": {
      "post": {
        "operationId": "pdfToDesignV1",
        "summary": "[Deprecated] Convert PDF to Design",
        "description": "**⚠️ Deprecated — legacy v1 endpoint.** This path is retained only for existing integrations. New integrations should use `POST /v2/open/pdf_to_design` (v2 unified Open API). Input, output, and billing are unchanged — this endpoint keeps its own independent legacy credit SKU.\n\n---\n\n**Cost**: 13 credits per processed page.\n\nExtract a structured, editable design tree out of one or more pages of a PDF. Each page becomes a fully-populated **`VisualElement`** tree — the same node shape as `image_to_design` — augmented with the vector / typography / transform fields that only PDFs carry.\n\n## What this API is for\n\nPDFs are vector documents: every paragraph, every shape, every gradient is stored as math, not pixels. This endpoint preserves that math instead of rasterizing it, so the tree you get back can be re-rendered, re-flowed, re-styled, or translated into another format without quality loss.\n\nTypical use cases:\n- **PDF → editable design**: open up an exported brochure / spec sheet / one-pager and turn it back into a Figma-like tree.\n- **PDF → PPT / HTML / code**: walk the tree to emit slides, web pages, or component code. (Our own `pdf_to_ppt` builds on this.)\n- **Content extraction**: pull every text run with its true font, size, and color, plus every vector shape with its actual path data.\n- **Asset salvage**: re-export embedded images and vector logos at original resolution.\n\nDifferences vs. `image_to_design`: input is a **PDF URL or PDF file**, output is **one tree per requested page** (not a single tree), and the per-node fields tagged \"PDF only\" in the shared schema **are** populated here (vector paths, character-level styling, transform matrices, etc.).\n\n## Sending the request\n\nThis endpoint supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with `pdf_url` and `page_no` when the PDF is already publicly reachable.\n2. **Direct upload mode**: send `multipart/form-data` with a local PDF file in the `pdf_file` field and one or more `page_no` fields.\n\nJSON example:\n\n```bash\ncurl 'https://api.codia.ai/v1/open/pdf_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -H 'Content-Type: application/json' \\\n  --data '{ \"pdf_url\": \"https://example.com/file.pdf\", \"page_no\": [0, 2, 5] }'\n```\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v1/open/pdf_to_design' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'pdf_file=@./file.pdf' \\\n  -F 'page_no=0' \\\n  -F 'page_no=2'\n```\n\n- **`pdf_url`** *(JSON mode, required)* — public URL of the PDF. Encrypted / password-protected PDFs are rejected.\n- **`pdf_file`** *(upload mode, required, binary)* — the PDF itself. Stream the file directly; do not base64-encode it.\n- **`page_no`** *(required)* — **0-indexed** page numbers. JSON mode uses an integer array; multipart mode repeats the `page_no` field once per page. Page count and file-size caps follow the global Codia API limits.\n\n## Response shape\n\nStandard Codia envelope; on success `data` carries:\n\n| Field | What it is | How to use it |\n|---|---|---|\n| `data.configuration` | Same `Configuration` object as `image_to_design` — `baseWidth`, `measurementUnit` (`px` \\| `pt`), `scalingFactor`. | Apply `scalingFactor` to every numeric value when rendering. `baseWidth` is the canvas width the trees were measured against (often page width in points or px, e.g. 1940). |\n| `data.size` | Page dimensions in document units: `{ width, height }`. | Use to set up artboard / viewport per page. All pages in a single PDF share this size. |\n| `data.pages[]` | One entry per requested page. Each entry is `{ visualElement: <Body root> }`. | Iterate pages in order; recurse into each `visualElement.childElements` to reach every region. |\n\n## Walking a page tree\n\nEach `data.pages[i].visualElement` is a `Body`-typed root with the same `VisualElement` shape used elsewhere in this API (see `image_to_design` for the full field guide). The headline pieces:\n\n- **`elementType`** — `Body` at the root, then `Layer` / `Group` / `Text` / `Image` / `Vector` / `Component` inside. PDFs use `Vector` heavily for shapes, gradients, and logos.\n- **`childElements`** — nested nodes; recurse depth-first.\n- **`layoutConfig` / `styleConfig` / `processingMeta`** — same semantics as `image_to_design`.\n- **`contentData`** — for `Text` reads `textValue`; for `Image` reads `imageSource` and/or the base64 in `imageData`; for `Vector` reads `vectorData` (see below).\n\n### PDF-specific fields you can actually use\n\nFields the shared schema tags \"PDF only\" — these are real, populated, and useful on this endpoint:\n\n- **`boundingBox`** *(`[x, y, width, height]`)* — exact node geometry in document coordinates. The most reliable positioning source for PDFs.\n- **`displayOrder`** — z-order within the page; lower = drawn first.\n- **`styleConfig.transformMatrix` / `extendedMatrix`** — affine transform applied to the node. `transformMatrix` is the 6-element PDF form `[a b c d e f]`; `extendedMatrix` is the CSS-compatible string. Apply this to handle rotated text, sheared images, etc.\n- **`styleConfig.characterData`** — per-character styling keyed by character index. Use this when a single text node mixes fonts, sizes, or colors (very common in PDFs).\n- **`styleConfig.cornerRadius`** — scalar fallback when the four-corner array isn't applicable.\n- **`styleConfig.isRightToLeft`** — direction flag for Arabic / Hebrew text.\n- **`contentData.vectorData`** — the actual vector path. Contains `pathItems[]` (each item is `{ pathType: \"l\" | \"c\", coordinates[] }` — `l` = line, `c` = cubic curve), plus `fillColor`, `strokeColor`, `strokeWidth`, `fillOpacity`, `strokeOpacity`, `lineCapStyle`, `lineJoinStyle`, `dashPattern`, `evenOddRule`, `isClosedPath`. Emit SVG `<path>` directly, or rebuild in your own renderer.\n- **`contentData.imageData`** — base64-encoded image bytes for embedded raster images (alongside or instead of `imageSource`).\n- **`contentData.svgTemplate`** — pre-rendered SVG markup template for compound vector content.\n- **`contentData.maskingMode` / `blendingType`** — PDF graphics-state knobs (clip / soft-mask, blend mode). Map to CSS `mix-blend-mode` / SVG `<mask>` as appropriate.\n\n### A minimal traversal\n\n```ts\nfor (const page of response.data.pages) {\n  console.log('page', page.visualElement.elementName, response.data.size);\n  walk(page.visualElement);\n}\n\nfunction walk(node, depth = 0) {\n  const pad = '  '.repeat(depth);\n  if (node.elementType === 'Text') console.log(pad, 'TXT', node.contentData?.textValue);\n  if (node.elementType === 'Image') console.log(pad, 'IMG', node.contentData?.imageSource);\n  if (node.elementType === 'Vector') console.log(pad, 'VEC', node.contentData?.vectorData?.pathItems?.length, 'segs');\n  node.childElements?.forEach(c => walk(c, depth + 1));\n}\n```\n\n## Inputs, limits, billing\n\n- **Input** — either JSON with `pdf_url` or `multipart/form-data` with `pdf_file`. Encrypted / password-protected PDFs are rejected.\n- **Page selection** — `page_no` uses **0-based** indices. JSON mode sends an integer array; multipart mode repeats the `page_no` field once per page. Out-of-range indices are silently dropped from the response.\n- **Billing** — consumes the unified Codia Open API balance (`GET /v2/open/credits`). 13 credits per processed page.\n- **Latency** — synchronous. Throughput is per-page; a long document may take many seconds. For very long PDFs, batch the request by `page_no` ranges and parallelize.\n- **Determinism** — same PDF + same `page_no` yields the same structural tree, but `elementId`s are regenerated each call; don't rely on cross-call ID stability.\n\n## Errors\n\nUses the standard envelope with non-zero `code` and a descriptive `message`. HTTP status codes:\n- `400` — bad input (missing `pdf_url` / `pdf_file`, malformed `page_no`, corrupt / encrypted PDF).\n- `401` / `403` — missing / wrong / unauthorized API key.\n- `402` — credits exhausted (see `/v2/open/auto_recharge`).\n- `429` — rate-limited; back off and retry.\n- `500` — internal error; safe to retry once.\n\n### `measurementUnit`\n\n`pdf_to_design` returns whichever unit the source PDF used. Most editorial / business PDFs report `pt` (PostScript points: 72 = 1 inch). PDFs exported from design tools at a specific pixel target may report `px`. Read this field per response and convert if your downstream renderer assumes one unit.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — fine for short PDFs. For documents with more than ~5 pages, prefer `POST /v2/open/tasks` with `operation=pdf_to_design` to avoid HTTP client timeouts, get webhook delivery, and support cancellation. Inputs and credit cost are identical to this endpoint.",
        "tags": [
          "Legacy (v1)"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pdf_url",
                  "page_no"
                ],
                "properties": {
                  "pdf_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the PDF to process. Must be directly fetchable by Codia."
                  },
                  "page_no": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "description": "0-indexed page numbers to process, e.g. [0, 2, 5]."
                  }
                }
              },
              "example": {
                "pdf_url": "https://example.com/file.pdf",
                "page_no": [
                  0,
                  2,
                  5
                ]
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "pdf_file",
                  "page_no"
                ],
                "properties": {
                  "pdf_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The PDF file to process. Sent as a multipart binary part."
                  },
                  "page_no": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "description": "Repeat the multipart page_no field once per 0-indexed page to process."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful conversion. `data.pages` is an array — one entry per processed page, each containing a `visualElement` tree with the same `VisualElement` shape as `image_to_design` (plus the PDF-only fields documented in the operation description). `data.size` carries the page dimensions and `data.configuration` carries the canvas scale / unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfToDesignResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "configuration": {
                      "scalingFactor": 1,
                      "baseWidth": 1940,
                      "measurementUnit": "px"
                    },
                    "size": {
                      "height": 1080,
                      "width": 1940
                    },
                    "pages": [
                      {
                        "visualElement": {
                          "elementId": "page_001",
                          "elementName": "Page 1",
                          "elementType": "Body",
                          "boundingBox": [
                            0,
                            0,
                            1940,
                            1080
                          ],
                          "displayOrder": 0,
                          "layoutConfig": {
                            "positionMode": "Normal"
                          },
                          "styleConfig": {
                            "widthSpec": {
                              "sizing": "FIXED",
                              "value": 1940
                            },
                            "heightSpec": {
                              "sizing": "FIXED",
                              "value": 1080
                            }
                          },
                          "processingMeta": {
                            "detectionScore": 1
                          },
                          "childElements": [
                            {
                              "elementId": "txt_h1",
                              "elementName": "Heading",
                              "elementType": "Text",
                              "boundingBox": [
                                120,
                                96,
                                1040,
                                64
                              ],
                              "displayOrder": 1,
                              "layoutConfig": {
                                "positionMode": "Absolute",
                                "absoluteAttrs": {
                                  "coord": {
                                    "x": 120,
                                    "y": 96
                                  }
                                }
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 1040
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 64
                                },
                                "textConfig": {
                                  "fontSize": 48,
                                  "fontStyle": "bold",
                                  "fontFamily": "Inter",
                                  "lineHeight": 56,
                                  "letterSpacing": 0,
                                  "textAlign": [
                                    "left"
                                  ]
                                },
                                "textColor": {
                                  "rgbValues": [
                                    17,
                                    17,
                                    17
                                  ],
                                  "hexCode": "#111111"
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.99
                              },
                              "contentData": {
                                "textValue": "Q4 2025 Review"
                              },
                              "childElements": []
                            },
                            {
                              "elementId": "vec_chart",
                              "elementName": "Pie slice",
                              "elementType": "Vector",
                              "boundingBox": [
                                120,
                                240,
                                320,
                                320
                              ],
                              "displayOrder": 2,
                              "layoutConfig": {
                                "positionMode": "Absolute",
                                "absoluteAttrs": {
                                  "coord": {
                                    "x": 120,
                                    "y": 240
                                  }
                                }
                              },
                              "styleConfig": {
                                "widthSpec": {
                                  "sizing": "FIXED",
                                  "value": 320
                                },
                                "heightSpec": {
                                  "sizing": "FIXED",
                                  "value": 320
                                }
                              },
                              "processingMeta": {
                                "detectionScore": 0.95
                              },
                              "contentData": {
                                "vectorData": {
                                  "pathItems": [
                                    {
                                      "pathType": "l",
                                      "coordinates": [
                                        {
                                          "xPosition": 160,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 160,
                                          "yPosition": 160
                                        }
                                      ]
                                    },
                                    {
                                      "pathType": "c",
                                      "coordinates": [
                                        {
                                          "xPosition": 160,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 247,
                                          "yPosition": 0
                                        },
                                        {
                                          "xPosition": 320,
                                          "yPosition": 73
                                        },
                                        {
                                          "xPosition": 320,
                                          "yPosition": 160
                                        }
                                      ]
                                    }
                                  ],
                                  "fillColor": {
                                    "rgbValues": [
                                      34,
                                      197,
                                      94
                                    ],
                                    "hexCode": "#22C55E"
                                  },
                                  "fillOpacity": 1,
                                  "strokeWidth": 0,
                                  "isClosedPath": true
                                }
                              },
                              "childElements": []
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "deprecated": true
      }
    },
    "/v1/open/remove_bg": {
      "post": {
        "operationId": "removeBackgroundV1",
        "summary": "[Deprecated] Remove Background",
        "description": "**⚠️ Deprecated — legacy v1 endpoint.** This path is retained only for existing integrations. New integrations should use `POST /v2/open/remove_bg` (v2 unified Open API). Input, output, and billing are unchanged — this endpoint keeps its own independent legacy credit SKU.\n\n---\n\n**Cost**: 13 credits per successful call.\n\nRemove Background supports two input modes:\n\n1. **JSON URL mode**: send `application/json` with the existing URL field.\n2. **Direct upload mode**: send `multipart/form-data` with a local image file in the `image` field. The compatible field name `file` is also accepted for the primary image. Codia checks credits before reading and uploading the file, so insufficient-credit requests return `402` without uploading anything.\n\nDirect upload example:\n\n```bash\ncurl 'https://api.codia.ai/v1/open/remove_bg' \\\n  -H 'Authorization: Bearer {codia_api_key}' \\\n  -F 'image=@./product.png'\n```\n\nCut out the foreground subject of an image and return a PNG with a fully transparent background. A synchronous, single-purpose endpoint — no model parameter, no prompt, no options.\n\n## What this API is for\n\nProduct photos for storefronts, profile avatars, sticker generation, compositing pipelines — any time you need the subject without its scene. Returns a true alpha-channel PNG (not a flat white background).\n\n## Request\n\n| Field | Required | Notes |\n|---|---|---|\n| `image_url` | yes | Public URL of the image to process. PNG, JPG, or WebP. Must be reachable from Codia's network. |\n\n## Response\n\nStandard envelope. Unique to this endpoint (not the shared `ImageUrlListSuccess`): `data.image_url` is a **single** string — a CDN URL to the processed transparent PNG. Download promptly; the URL is pre-signed.\n\n```json\n{ \"code\": 0, \"message\": \"ok\", \"data\": { \"image_url\": \"https://cdn.codia.ai/bg-removed/abc.png\" } }\n```\n\n## Billing & limits\n\n- 13 credits per successful call, from the unified Codia Open API balance.\n- Synchronous; typically completes within a few seconds.\n- Output is always PNG with an alpha channel, at the input image's resolution.\n\n## Errors\n\nStandard set — `400` for bad input, `401`/`403` auth, `402` credits, `429` rate limit, `500` internal.\n\n> **Sync vs. async**\n> This synchronous endpoint returns the result inline — the simplest path. For production pipelines that need webhook delivery, automatic retry with idempotency, or explicit task cancellation, call `POST /v2/open/tasks` with `operation=remove_bg` instead. Inputs and credit cost are identical to this endpoint.",
        "tags": [
          "Legacy (v1)"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "image_url"
                ],
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public URL of the image to process"
                  }
                }
              },
              "example": {
                "image_url": "https://example.com/product.jpg"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Source image file"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Compatible source image file field (deprecated: use `image` instead).",
                    "deprecated": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful background removal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveBgResponse"
                },
                "example": {
                  "code": 0,
                  "message": "ok",
                  "data": {
                    "image_url": "https://processed-image-url.com/result.png"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "deprecated": true
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key obtained from [Personal Center > API Keys](https://codia.ai/dashboard/developer). Your key works across all subscribed capabilities."
      },
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Backward-compatible alias for bearer API key authentication."
      }
    },
    "schemas": {
      "ResponseEnvelope": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "description": "0 for success, non-zero for errors"
          },
          "message": {
            "type": "string",
            "description": "Human-readable status or error description"
          },
          "data": {
            "description": "Response payload (varies by endpoint)"
          }
        }
      },
      "ImageToDesignResponse": {
        "type": "object",
        "description": "Envelope returned by `POST /v2/open/image_to_design`. `code = 0` on success; `data` carries the hierarchical design tree (`visualElement`) plus the canvas-level rendering config (`configuration`). Walk `visualElement.childElements` recursively to reach every detected region — see the operation description for a field-by-field walkthrough.",
        "properties": {
          "code": {
            "type": "integer",
            "description": "0 on success; non-zero values mirror the HTTP status family (400/401/402/403/429/500).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "Human-readable status. `\"ok\"` on success, otherwise an error reason.",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "visualElement": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/VisualElement"
                  }
                ],
                "description": "Root of the design tree. Always `elementType: \"Body\"`. Its `childElements` form the rest of the page."
              },
              "configuration": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Configuration"
                  }
                ],
                "description": "Canvas-level rendering settings the tree was measured against. Apply `scalingFactor` to every numeric value when rendering."
              }
            }
          }
        }
      },
      "PdfToDesignResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "configuration": {
                "$ref": "#/components/schemas/Configuration"
              },
              "pages": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "visualElement": {
                      "$ref": "#/components/schemas/VisualElement"
                    }
                  }
                }
              },
              "size": {
                "type": "object",
                "properties": {
                  "width": {
                    "type": "number"
                  },
                  "height": {
                    "type": "number"
                  }
                }
              }
            }
          }
        }
      },
      "RemoveBgResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "image_url": {
                "type": "string",
                "format": "uri",
                "description": "URL of the processed image with transparent background"
              }
            }
          }
        }
      },
      "ImageUrlListResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "image_urls": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uri"
                },
                "description": "Generated or processed image URLs"
              }
            }
          }
        }
      },
      "ImageDescribeResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "description": {
                "type": "string",
                "description": "Natural-language image description"
              }
            }
          }
        }
      },
      "ImageLayeringResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "type": {
                "type": "integer",
                "description": "Layering result type"
              },
              "dsl": {
                "type": "string",
                "description": "JSON-encoded editable layer tree"
              }
            }
          }
        }
      },
      "ImageUpscaleRequest": {
        "type": "object",
        "required": [
          "image_url"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the image to upscale"
          },
          "model": {
            "type": "string",
            "description": "Model that performs the upscale. Defaults to `codia_image_v2`. Credits follow the `image_upscale` pricing table.\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| default / `codia_image_v2` | 16 | - | - | - |\n| `ideogram_v3` | 16 | 16 | 16 | - |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `gpt_image` | 56 | 56 | 112 | - |",
            "default": "codia_image_v2"
          }
        }
      },
      "ImageReplaceBackgroundRequest": {
        "type": "object",
        "required": [
          "image_url",
          "prompt"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the source image"
          },
          "prompt": {
            "type": "string",
            "description": "Text description of the desired new background"
          },
          "model": {
            "type": "string",
            "description": "Model that synthesises the new background. Defaults to `nano_banana_2`. All supported models currently cost 13 credits per request.\n\n| Model | Best for | Credits |\n| --- | --- | ---: |\n| `nano_banana_2` | Fast background swaps (Gemini 3.1 Flash). Good default for most images. | 13 |\n| `nano_banana_pro` | Studio-quality 4K backgrounds with realistic lighting and reflections. | 13 |\n| `ideogram_v3` | Stylized, illustrative, or text-heavy backgrounds. | 13 |",
            "default": "nano_banana_2"
          }
        }
      },
      "ImageObjectEraseRequest": {
        "type": "object",
        "required": [
          "image_url",
          "mask_url"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the source image"
          },
          "mask_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the mask image (white = area to erase, black = keep)"
          },
          "model": {
            "type": "string",
            "description": "Model that performs the inpainting. Defaults to `codia_image_v2`. All supported models currently cost 13 credits per request.\n\n| Model | Best for | Credits |\n| --- | --- | ---: |\n| `codia_image_v2` | Clean, fast inpainting for photos and UI. Best when the surrounding context is simple. | 13 |\n| `nano_banana_2` | AI-aware erase that reconstructs complex context (Gemini 3.1 Flash). | 13 |\n| `nano_banana_pro` | High-fidelity erase with intricate background reconstruction (Gemini 3 Pro). | 13 |",
            "default": "codia_image_v2"
          }
        }
      },
      "ImageDescribeRequest": {
        "type": "object",
        "required": [
          "image_url"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the image to describe"
          },
          "model": {
            "type": "string",
            "description": "Vision model that captions/describes the image. Defaults to `nano_banana_2`. All supported models currently cost 5 credits per request.\n\n| Model | Best for | Credits |\n| --- | --- | ---: |\n| `nano_banana_2` | Fast, accurate captions for prompt extraction (Gemini 3.1 Flash). | 5 |\n| `nano_banana_pro` | Long, detailed multi-aspect descriptions (Gemini 3 Pro). | 5 |\n| `ideogram_v3` | Caption focused on style, composition, and typography. | 5 |",
            "default": "nano_banana_2"
          }
        }
      },
      "ImageWatermarkRemoveRequest": {
        "type": "object",
        "required": [
          "image_url"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the image with watermark to remove"
          }
        }
      },
      "ImageGenerateRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Text description of the image to generate"
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Number of images to generate. Credits are charged per generated image."
          },
          "size": {
            "type": "string",
            "example": "1024x1024",
            "description": "Output image dimensions as `WIDTHxHEIGHT`. Common sizes: `1024x1024`, `1536x1024`, `1024x1536`"
          },
          "model": {
            "type": "string",
            "description": "Model that generates the image. Defaults to `nano_banana_2`. Supported models match Codia Studio for text-to-image generation. Credits are configured in `pricing.yaml` and returned by `GET /v2/open/limits`.\n\n| Model | Best for |\n| --- | --- |\n| `nano_banana_2` | Fast everyday generation (Gemini 3.1 Flash). |\n| `nano_banana_pro` | Studio-quality 4K visuals with advanced reasoning (Gemini 3 Pro). |\n| `gpt_image` | Crisp in-image text, high-fidelity references, flexible sizes. Supports `quality` (`low`/`medium`/`high`/`auto`) and `background` (`transparent`/`opaque`/`auto`). |\n| `seedream_5` | Photorealistic imagery with fine detail. Sizes are 1920-based, e.g. `1920x1080`. |\n| `seedream_4_5` | Reliable photorealism, great for portraits. Sizes are 1920-based. |\n| `recraft_v4` | Illustrations, icons, and vector-style art with precise style control. |\n| `flux_2_pro` | High-quality FLUX generation by Black Forest Labs. |\n| `flux_2_max` | Maximum-quality FLUX tier for premium results. |\n| `ideogram_v3` | Excellent typography and creative compositions. |",
            "default": "nano_banana_2"
          },
          "quality": {
            "type": "string",
            "description": "Quality preset (model-dependent). For `gpt_image`: `low`, `medium`, `high`, or `auto`; omitted/`auto` uses the medium billing bucket.",
            "enum": [
              "low",
              "medium",
              "high",
              "auto"
            ]
          },
          "background": {
            "type": "string",
            "description": "Background mode (model-dependent). For `gpt_image`: `transparent`, `opaque`, or `auto`.",
            "enum": [
              "transparent",
              "opaque",
              "auto"
            ]
          },
          "reference_images": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Reference image URLs for style guidance (model-dependent)"
          }
        }
      },
      "ImageToImageRequest": {
        "type": "object",
        "required": [
          "image_url",
          "prompt"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the source image to transform"
          },
          "prompt": {
            "type": "string",
            "description": "Text description of the desired transformation"
          },
          "model": {
            "type": "string",
            "description": "Model that performs the image-to-image transformation. Defaults to `nano_banana_2`. Supported models match Codia Studio for image-to-image generation. Credits are configured in `pricing.yaml` and returned by `GET /v2/open/limits`.\n\n| Model | Best for |\n| --- | --- |\n| `nano_banana_2` | Fast image-to-image edits guided by a text prompt (Gemini 3.1 Flash). |\n| `nano_banana_pro` | Studio-quality 4K transformations with advanced reasoning (Gemini 3 Pro). |\n| `gpt_image` | High-fidelity reference-driven edits with crisp text. Supports `quality` (`low`/`medium`/`high`/`auto`) and `background` (`transparent`/`opaque`/`auto`). |\n| `seedream_5` | Photorealistic transformations with fine detail. Sizes are 1920-based. |\n| `seedream_4_5` | Reliable photorealistic transformations. Sizes are 1920-based. |\n| `recraft_v4` | Convert photos into illustrations, icons, or vector-style art. |\n| `flux_2_pro` | High-quality FLUX-driven edits. |\n| `flux_2_max` | Maximum-quality FLUX tier for premium edits. |\n| `ideogram_v3` | Edits that retain or introduce strong typography. |",
            "default": "nano_banana_2"
          },
          "size": {
            "type": "string",
            "description": "Optional output dimensions as `WIDTHxHEIGHT`, e.g. `1024x1024`. For `gpt_image`, size contributes to the pricing key together with `quality`."
          },
          "quality": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "auto"
            ],
            "description": "GPT Image only. Controls OpenAI image quality for `model=gpt_image`. Omitted or `auto` uses the medium billing bucket. Ignored by non-GPT models."
          },
          "background": {
            "type": "string",
            "enum": [
              "transparent",
              "opaque",
              "auto"
            ],
            "description": "GPT Image only. Requests transparent or opaque background when supported. Ignored by non-GPT models."
          }
        }
      },
      "ImageRemixRequest": {
        "type": "object",
        "required": [
          "image_url",
          "prompt"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the source image to remix"
          },
          "prompt": {
            "type": "string",
            "description": "Text description of the desired remix style"
          },
          "model": {
            "type": "string",
            "description": "Model that performs the remix. Defaults to `nano_banana_2`. Credits follow the `image_remix` model/resolution table.\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `gpt_image` | 8 | 8 | 34 | 34 |\n| `seedream_5` | 9 | 9 | 9 | 9 |\n| `seedream_4_5` | 11 | 11 | 11 | 11 |\n| `recraft_v4` | 11 | 11 | 67 | - |\n| `flux_2_pro` | 8 | 8 | 20 | - |\n| `flux_2_max` | 19 | 19 | 43 | - |\n| `ideogram_v3` | 16 | 16 | - | - |",
            "default": "nano_banana_2"
          },
          "size": {
            "type": "string",
            "example": "1024x1024",
            "description": "Output image dimensions as `WIDTHxHEIGHT`"
          }
        }
      },
      "ImageReframeRequest": {
        "type": "object",
        "required": [
          "image_url",
          "resolution"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the source image to reframe"
          },
          "resolution": {
            "type": "string",
            "example": "1920x1080",
            "description": "Target resolution as `WIDTHxHEIGHT`. The image will be extended to fill this size."
          },
          "model": {
            "type": "string",
            "description": "Model that performs the reframe / outpaint. Defaults to `nano_banana_2`. Credits follow the `image_reframe` model/resolution table.\n\n| Model | Default | 1K | 2K | 4K |\n| --- | ---: | ---: | ---: | ---: |\n| `gpt_image` | 8 | 8 | 34 | 34 |\n| `nano_banana_2` | 18 | 18 | 27 | 40 |\n| `nano_banana_pro` | 36 | 36 | 36 | 64 |\n| `seedream_5` | 9 | 9 | 9 | 9 |\n| `seedream_4_5` | 11 | 11 | 11 | 11 |\n| `flux_2_pro` | 8 | 8 | 20 | - |\n| `flux_2_max` | 19 | 19 | 43 | - |\n| `ideogram_v3` | 16 | 16 | - | - |",
            "default": "nano_banana_2"
          }
        }
      },
      "ImageLayeringRequest": {
        "type": "object",
        "required": [
          "image_url"
        ],
        "properties": {
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the image to decompose into layers"
          }
        }
      },
      "Configuration": {
        "type": "object",
        "description": "Global rendering settings for the design output",
        "properties": {
          "baseWidth": {
            "type": "number",
            "description": "Base design width in the specified unit",
            "default": 375
          },
          "measurementUnit": {
            "type": "string",
            "description": "Unit of measurement",
            "enum": [
              "px",
              "pt"
            ],
            "default": "px"
          },
          "scalingFactor": {
            "type": "number",
            "description": "Scaling multiplier applied to all values",
            "default": 1
          }
        }
      },
      "VisualElement": {
        "type": "object",
        "description": "A node in the visual element tree describing a UI element, its position, style, and content.",
        "required": [
          "elementId",
          "elementName",
          "elementType",
          "layoutConfig",
          "styleConfig",
          "processingMeta"
        ],
        "properties": {
          "elementId": {
            "type": "string",
            "description": "Unique identifier"
          },
          "elementName": {
            "type": "string",
            "description": "Display name"
          },
          "elementType": {
            "type": "string",
            "description": "Element category",
            "enum": [
              "Body",
              "Layer",
              "Image",
              "Text",
              "Component",
              "Vector",
              "Group"
            ]
          },
          "displayName": {
            "type": "string",
            "description": "English display name"
          },
          "layoutConfig": {
            "$ref": "#/components/schemas/LayoutConfig"
          },
          "styleConfig": {
            "$ref": "#/components/schemas/StyleConfig"
          },
          "processingMeta": {
            "$ref": "#/components/schemas/ProcessingMeta"
          },
          "childElements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VisualElement"
            },
            "description": "Nested child elements"
          },
          "contentData": {
            "$ref": "#/components/schemas/ContentData"
          },
          "componentSpec": {
            "$ref": "#/components/schemas/ComponentSpec"
          },
          "boundingBox": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Bounding box coordinates [x, y, width, height] (PDF only)"
          },
          "displayOrder": {
            "type": "number",
            "description": "Rendering order within the page (PDF only)"
          }
        }
      },
      "LayoutConfig": {
        "type": "object",
        "description": "Controls how an element is positioned within its parent",
        "required": [
          "positionMode"
        ],
        "properties": {
          "positionMode": {
            "type": "string",
            "enum": [
              "Normal",
              "Absolute",
              "Relative",
              "Flex"
            ],
            "description": "Position mode"
          },
          "flexibleMode": {
            "type": "string",
            "description": "Flex layout type"
          },
          "flexAttributes": {
            "type": "object",
            "properties": {
              "alignItems": {
                "type": "string"
              },
              "justifyContent": {
                "type": "string"
              },
              "flexDirection": {
                "type": "string"
              },
              "flexWrap": {
                "type": "string"
              }
            },
            "description": "Flex properties"
          },
          "absoluteAttrs": {
            "type": "object",
            "properties": {
              "align": {
                "type": "string"
              },
              "coord": {
                "type": "object"
              },
              "orginCoord": {
                "type": "object"
              }
            },
            "description": "Absolute position attributes"
          }
        }
      },
      "StyleConfig": {
        "type": "object",
        "description": "Defines the visual appearance of an element",
        "properties": {
          "widthSpec": {
            "$ref": "#/components/schemas/DimensionSpec"
          },
          "heightSpec": {
            "$ref": "#/components/schemas/DimensionSpec"
          },
          "textConfig": {
            "type": "object",
            "properties": {
              "fontSize": {
                "type": "number"
              },
              "fontStyle": {
                "type": "string",
                "enum": [
                  "normal",
                  "bold",
                  "italic",
                  "semi_bold"
                ]
              },
              "textAlign": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "fontFamily": {
                "type": "string"
              },
              "lineHeight": {
                "type": "number",
                "description": "Line height in CSS px"
              },
              "letterSpacing": {
                "type": "number",
                "description": "Letter spacing in CSS px (relative to OCR-measured font-size). The Studio canvas converts this to a percentage of font-size at import time, matching Figma semantics."
              }
            }
          },
          "textColor": {
            "$ref": "#/components/schemas/VisualColor"
          },
          "borderConfig": {
            "type": "object",
            "properties": {
              "borderWidth": {
                "type": "number"
              },
              "borderStyle": {
                "type": "string"
              },
              "borderColor": {
                "$ref": "#/components/schemas/VisualColor"
              },
              "borderRadius": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            }
          },
          "backgroundConfig": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "COLOR"
                  },
                  "backgroundColor": {
                    "$ref": "#/components/schemas/VisualColor"
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "IMAGE"
                  },
                  "imageUrl": {
                    "type": "string"
                  },
                  "backgroundSize": {
                    "type": "string"
                  },
                  "backgroundPosition": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "LINEAR_GRADIENT"
                  },
                  "deg": {
                    "type": "number"
                  },
                  "gradientStops": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "$ref": "#/components/schemas/VisualColor"
                        },
                        "position": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            ]
          },
          "effectsList": {
            "type": "array",
            "description": "Visual effects (shadows, blurs)"
          },
          "paddingValues": {
            "description": "Padding values"
          },
          "opacityLevel": {
            "type": "number",
            "description": "Opacity (0–255)"
          },
          "overflowMode": {
            "type": "array",
            "description": "Overflow behavior"
          },
          "rotationAngle": {
            "type": "number",
            "description": "Rotation in degrees"
          },
          "characterData": {
            "type": "object",
            "description": "Per-character styling keyed by character index (PDF only)"
          },
          "transformMatrix": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "6-element affine transformation matrix (PDF only)"
          },
          "cornerRadius": {
            "type": "number",
            "description": "Border corner radius (PDF only)"
          },
          "isRightToLeft": {
            "type": "boolean",
            "description": "Right-to-left text direction (PDF only)"
          },
          "extendedMatrix": {
            "type": "string",
            "description": "CSS-format transformation matrix (PDF only)"
          }
        }
      },
      "DimensionSpec": {
        "type": "object",
        "properties": {
          "sizing": {
            "type": "string",
            "enum": [
              "FIXED",
              "FILL",
              "FIT_CONTENT"
            ],
            "description": "FIXED (exact size), FILL (stretch to parent), FIT_CONTENT (shrink to content)"
          },
          "value": {
            "type": "number",
            "description": "Dimension value"
          }
        }
      },
      "VisualColor": {
        "type": "object",
        "properties": {
          "rgbValues": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "minItems": 3,
            "maxItems": 3,
            "description": "RGB values [r, g, b]"
          },
          "hexCode": {
            "type": "string",
            "description": "Hex color code"
          }
        }
      },
      "ContentData": {
        "type": "object",
        "description": "Defines what an element displays",
        "properties": {
          "textValue": {
            "type": "string",
            "description": "The text string (Text elements)"
          },
          "imageSource": {
            "type": "string",
            "description": "Image URL (Image elements)"
          },
          "colorReference": {
            "type": "string",
            "description": "Color token reference"
          },
          "componentReference": {
            "type": "string",
            "description": "Reference to a component definition"
          },
          "componentAttributes": {
            "type": "object",
            "description": "Props passed to the component"
          },
          "vectorData": {
            "$ref": "#/components/schemas/VectorData"
          },
          "maskingMode": {
            "type": "string",
            "description": "Element masking mode (PDF only)"
          },
          "blendingType": {
            "type": "string",
            "description": "Layer blending mode (PDF only)"
          },
          "svgTemplate": {
            "type": "string",
            "description": "SVG markup template (PDF only)"
          },
          "imageData": {
            "type": "string",
            "description": "Base64-encoded image data (PDF only)"
          }
        }
      },
      "ProcessingMeta": {
        "type": "object",
        "description": "Metadata about how the element was detected",
        "properties": {
          "surfaceArea": {
            "type": "number",
            "description": "Element area in pixels"
          },
          "detectionScore": {
            "type": "number",
            "description": "Confidence score (0.0–1.0)"
          },
          "textContainerized": {
            "type": "boolean",
            "description": "Whether text is inside a container"
          }
        }
      },
      "ComponentSpec": {
        "type": "object",
        "description": "Reusable component definition (when elementType is Component)",
        "properties": {
          "componentId": {
            "type": "string",
            "description": "Unique component identifier"
          },
          "componentName": {
            "type": "string",
            "description": "Display name"
          },
          "sourceCode": {
            "type": "string",
            "description": "Implementation code"
          },
          "configOptions": {
            "type": "object",
            "description": "Accepted configuration properties"
          }
        }
      },
      "VectorData": {
        "type": "object",
        "description": "Vector path data (PDF only)",
        "properties": {
          "pathItems": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pathType": {
                  "type": "string",
                  "enum": [
                    "l",
                    "c"
                  ],
                  "description": "l for line, c for cubic curve"
                },
                "coordinates": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "xPosition": {
                        "type": "number"
                      },
                      "yPosition": {
                        "type": "number"
                      }
                    }
                  }
                }
              }
            }
          },
          "evenOddRule": {
            "type": "boolean",
            "description": "Fill rule"
          },
          "vectorType": {
            "type": "string",
            "description": "Vector type identifier"
          },
          "strokeColor": {
            "$ref": "#/components/schemas/VisualColor"
          },
          "strokeWidth": {
            "type": "number"
          },
          "strokeOpacity": {
            "type": "number",
            "description": "Stroke opacity (0.0–1.0)"
          },
          "fillColor": {
            "$ref": "#/components/schemas/VisualColor"
          },
          "fillOpacity": {
            "type": "number",
            "description": "Fill opacity (0.0–1.0)"
          },
          "lineCapStyle": {
            "type": "string",
            "enum": [
              "butt",
              "round",
              "square"
            ]
          },
          "lineJoinStyle": {
            "type": "string",
            "enum": [
              "miter",
              "round",
              "bevel"
            ]
          },
          "dashPattern": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "isClosedPath": {
            "type": "boolean"
          }
        }
      },
      "SvgConverterCreateRequest": {
        "type": "object",
        "required": [
          "pic_url"
        ],
        "properties": {
          "pic_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the raster image to vectorize (PNG, JPG, GIF, WebP) (deprecated: use `image_url` instead).",
            "deprecated": true
          },
          "file_name": {
            "type": "string",
            "description": "Original file name (optional, used for record-keeping)"
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the raster image to vectorize. Canonical field name (preferred over `pic_url`)."
          }
        }
      },
      "SvgConverterCreateResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "record_id": {
                "type": "string",
                "format": "uuid",
                "description": "Use this ID to poll for the conversion result"
              }
            }
          }
        }
      },
      "SvgConverterGetRequest": {
        "type": "object",
        "required": [
          "record_id"
        ],
        "properties": {
          "record_id": {
            "type": "string",
            "format": "uuid",
            "description": "Record ID returned by /v2/open/svg_converter/create"
          }
        }
      },
      "SvgConverterGetResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "svg_url": {
                "type": "string",
                "format": "uri",
                "description": "Download URL for the generated SVG file. Present when status is complete."
              },
              "svg_convert_status": {
                "type": "string",
                "enum": [
                  "doing",
                  "complete",
                  "failed"
                ],
                "description": "Current conversion status"
              }
            }
          }
        }
      },
      "SvgConverterLimitResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "pic_height": {
                "type": "integer",
                "description": "Maximum image height in pixels"
              },
              "pic_width": {
                "type": "integer",
                "description": "Maximum image width in pixels"
              },
              "file_size": {
                "type": "integer",
                "description": "Maximum file size in bytes"
              },
              "layer_credit_unit": {
                "type": "integer",
                "description": "Credits consumed per conversion"
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "null"
          }
        }
      },
      "OpenAPITaskOperation": {
        "type": "string",
        "enum": [
          "image_to_design",
          "pdf_to_design",
          "pdf_to_ppt",
          "remove_bg",
          "image_generate",
          "image_to_image",
          "image_remix",
          "image_reframe",
          "image_upscale",
          "image_object_erase",
          "image_watermark_remove",
          "image_layering",
          "image_describe",
          "image_replace_background"
        ],
        "description": "Supported asynchronous Open API operations."
      },
      "OpenAPITaskStatus": {
        "type": "string",
        "enum": [
          "pending",
          "processing",
          "succeeded",
          "failed",
          "canceled"
        ],
        "description": "Get the latest state for one Open API task. The request must use the same API key that created the task. Terminal states are `succeeded`, `failed`, and `canceled`."
      },
      "OpenAPITaskCreateRequest": {
        "type": "object",
        "required": [
          "operation",
          "input"
        ],
        "properties": {
          "operation": {
            "$ref": "#/components/schemas/OpenAPITaskOperation"
          },
          "input": {
            "type": "object",
            "additionalProperties": true,
            "description": "Operation-specific input. URL-based image operations require image_url; image_object_erase also requires mask_url; prompt-based image operations require prompt; pdf_to_design requires pdf_url; pdf_to_ppt accepts either upload_id from /v2/open/uploads or a caller-hosted pdf_url. For PDF operations, omit page_no or send an empty array to process every page up to account limits."
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional HTTPS webhook URL called when the task reaches a terminal state."
          }
        }
      },
      "OpenAPITaskCreateResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "task_id": {
                "type": "string",
                "format": "uuid"
              },
              "operation": {
                "$ref": "#/components/schemas/OpenAPITaskOperation"
              },
              "status": {
                "type": "string",
                "enum": [
                  "pending",
                  "processing"
                ]
              },
              "created_at": {
                "type": "integer",
                "description": "Unix timestamp in seconds."
              }
            }
          }
        }
      },
      "OpenAPITaskItem": {
        "type": "object",
        "properties": {
          "task_id": {
            "type": "string",
            "format": "uuid"
          },
          "operation": {
            "$ref": "#/components/schemas/OpenAPITaskOperation"
          },
          "status": {
            "$ref": "#/components/schemas/OpenAPITaskStatus"
          },
          "progress": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "description": "Operation-specific result. pdf_to_ppt success includes ppt_url; design operations return design DSL data."
          },
          "error": {
            "type": "string",
            "description": "Human-readable failure message, present when failed or canceled."
          },
          "error_code": {
            "type": "string",
            "description": "Machine-readable failure code, present when failed or canceled."
          },
          "created_at": {
            "type": "integer",
            "description": "Unix timestamp in seconds."
          },
          "updated_at": {
            "type": "integer",
            "description": "Unix timestamp in seconds."
          },
          "started_at": {
            "type": "integer",
            "description": "Unix timestamp in seconds, present after processing starts."
          },
          "completed_at": {
            "type": "integer",
            "description": "Unix timestamp in seconds, present for terminal states."
          }
        }
      },
      "OpenAPITaskGetResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "$ref": "#/components/schemas/OpenAPITaskItem"
          }
        }
      },
      "OpenAPITaskListResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "tasks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OpenAPITaskItem"
                }
              },
              "has_more": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "OpenAPITaskCancelResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "task_id": {
                "type": "string",
                "format": "uuid"
              },
              "operation": {
                "$ref": "#/components/schemas/OpenAPITaskOperation"
              },
              "status": {
                "$ref": "#/components/schemas/OpenAPITaskStatus"
              },
              "credits_reserved": {
                "type": "number",
                "format": "float",
                "description": "Credits originally reserved or deducted for the task.",
                "example": 15
              },
              "credits_charged": {
                "type": "number",
                "format": "float",
                "description": "Credits finally charged after cancellation. For pdf_to_ppt, this equals the number of pages already converted multiplied by the current per-page price.",
                "example": 10
              },
              "credits_refunded": {
                "type": "number",
                "format": "float",
                "description": "Credits refunded by the cancel call. For pdf_to_ppt, this equals the number of pages not yet converted multiplied by the current per-page price.",
                "example": 5
              }
            }
          }
        }
      },
      "CreditsResponse": {
        "type": "object",
        "description": "Envelope returned by GET /v2/open/credits. Carries the live balance plus monthly/topup split, current billing period bounds, and the user's auto-recharge configuration.",
        "properties": {
          "code": {
            "type": "integer",
            "description": "0 on success; non-zero mirrors HTTP status.",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "Human-readable status string.",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "product": {
                "type": "string",
                "description": "Always `open_api` for this endpoint.",
                "example": "open_api"
              },
              "available_credits": {
                "type": "number",
                "description": "Live total available credit balance (monthly + topup). `-1` means unlimited (Enterprise).",
                "example": 4280
              },
              "monthly_credits_remaining": {
                "type": "number",
                "description": "Credits remaining in the current monthly allotment. `-1` for unlimited.",
                "example": 4000
              },
              "topup_credits_remaining": {
                "type": "number",
                "description": "Credits remaining from one-time top-ups (never expire within the credit period).",
                "example": 280
              },
              "used_credits": {
                "type": "number",
                "description": "Credits consumed in the current billing period.",
                "example": 720
              },
              "total_credits": {
                "type": "number",
                "description": "Total credits granted in the current period (monthly + topups). `-1` for unlimited.",
                "example": 5000
              },
              "current_period_start": {
                "type": "string",
                "format": "date-time",
                "description": "Start of the current billing period (RFC3339).",
                "example": "2026-05-01T00:00:00Z"
              },
              "current_period_end": {
                "type": "string",
                "format": "date-time",
                "description": "End of the current billing period (RFC3339).",
                "example": "2026-06-01T00:00:00Z"
              },
              "next_reset_at": {
                "type": "string",
                "format": "date-time",
                "description": "Wall-clock time when monthly credits reset. Equals `current_period_end`.",
                "example": "2026-06-01T00:00:00Z"
              },
              "auto_recharge_enabled": {
                "type": "boolean",
                "description": "Whether auto-recharge is currently on.",
                "example": true
              },
              "low_balance_threshold": {
                "type": "integer",
                "description": "Recharge fires when `available_credits` falls at or below this.",
                "example": 200
              },
              "auto_recharge_credits": {
                "type": "integer",
                "enum": [
                  100,
                  200,
                  500
                ],
                "description": "Credits added per auto-recharge.",
                "example": 500
              },
              "monthly_max_recharge_credits": {
                "type": "integer",
                "description": "Hard cap on auto-recharge spend per calendar month.",
                "example": 5000
              }
            }
          }
        }
      },
      "UsageListResponse": {
        "type": "object",
        "description": "Envelope returned by GET /v2/open/usage. Paginated, offset-based list of per-call usage records.",
        "properties": {
          "code": {
            "type": "integer",
            "description": "0 on success; non-zero mirrors HTTP status.",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "Human-readable status string.",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "description": "Per-call records, newest first.",
                "items": {
                  "type": "object",
                  "properties": {
                    "request_id": {
                      "type": "string",
                      "description": "Unique record ID for the call.",
                      "example": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    "endpoint": {
                      "type": "string",
                      "description": "API path that was called (`/v2/open/image/*` is collapsed across image endpoints).",
                      "example": "/v2/open/image_to_design"
                    },
                    "operation": {
                      "type": "string",
                      "description": "Operation name (e.g. `image_to_design`, `pdf_to_ppt`).",
                      "example": "image_to_design"
                    },
                    "credits_used": {
                      "type": "number",
                      "description": "Credits charged for this call.",
                      "example": 1
                    },
                    "status": {
                      "type": "string",
                      "description": "Outcome of the call.",
                      "enum": [
                        "success"
                      ],
                      "example": "success"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the call was recorded (RFC3339, UTC).",
                      "example": "2026-05-28T08:23:11Z"
                    }
                  }
                }
              },
              "total": {
                "type": "integer",
                "description": "Total records matching the date range.",
                "example": 137
              },
              "page": {
                "type": "integer",
                "description": "Current page (1-indexed).",
                "example": 1
              },
              "page_size": {
                "type": "integer",
                "description": "Records per page.",
                "example": 50
              },
              "has_more": {
                "type": "boolean",
                "description": "Whether more records exist after this page.",
                "example": true
              }
            }
          }
        }
      },
      "AutoRechargeResponse": {
        "type": "object",
        "description": "Envelope returned by GET/POST /v2/open/auto_recharge. `data` is the live auto-recharge configuration.",
        "properties": {
          "code": {
            "type": "integer",
            "description": "0 on success; non-zero mirrors HTTP status.",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "Human-readable status string.",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "Whether auto-recharge is currently on.",
                "example": true
              },
              "threshold_credits": {
                "type": "integer",
                "description": "Recharge fires when the live balance falls at or below this number.",
                "example": 200
              },
              "recharge_credits": {
                "type": "integer",
                "enum": [
                  100,
                  200,
                  500
                ],
                "description": "Credits added per recharge.",
                "example": 500
              },
              "monthly_max_recharge_credits": {
                "type": "integer",
                "description": "Hard cap on auto-recharge spend per calendar month.",
                "example": 5000
              }
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Payload Codia POSTs to your `callback_url` when a task reaches a terminal state. See the Webhook callback section of `/v2/open/tasks`.",
        "required": [
          "task_id",
          "operation",
          "status",
          "timestamp"
        ],
        "properties": {
          "task_id": {
            "type": "string",
            "format": "uuid",
            "description": "Task identifier returned by POST /v2/open/tasks."
          },
          "operation": {
            "type": "string",
            "description": "Operation type, e.g. `pdf_to_ppt`, `image_to_design`."
          },
          "status": {
            "type": "string",
            "enum": [
              "succeeded",
              "failed",
              "canceled"
            ],
            "description": "Terminal status of the task."
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix timestamp (seconds) when the event was emitted."
          },
          "result": {
            "type": "object",
            "description": "Operation-specific result payload; same shape as GET /v2/open/tasks/{task_id} `result`. Empty / omitted on failure.",
            "additionalProperties": true
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message when `status` is `failed`. Empty string on success."
          }
        }
      },
      "OpenAPILimitsResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "subscription_plan": {
                "type": "string",
                "description": "Current account plan id, for example free, starter, pro, ultra, team, business, or enterprise."
              },
              "image": {
                "type": "object",
                "properties": {
                  "max_width": {
                    "type": "integer",
                    "description": "Maximum image width in pixels. -1 means no plan cap."
                  },
                  "max_height": {
                    "type": "integer",
                    "description": "Maximum image height in pixels. -1 means no plan cap."
                  },
                  "max_file_size_bytes": {
                    "type": "integer"
                  }
                }
              },
              "pdf": {
                "type": "object",
                "properties": {
                  "max_pages": {
                    "type": "integer"
                  },
                  "max_file_size_bytes": {
                    "type": "integer"
                  },
                  "max_title_len": {
                    "type": "integer"
                  }
                }
              },
              "task": {
                "type": "object",
                "properties": {
                  "max_processing_per_user_per_operation": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer"
                    },
                    "description": "Per-operation processing concurrency cap. Additional accepted tasks wait in pending state."
                  },
                  "max_queue_depth_per_user_per_operation": {
                    "type": "integer",
                    "description": "Hard cap for pending + processing tasks per user per operation."
                  }
                }
              },
              "rate_limits": {
                "type": "object",
                "properties": {
                  "global_per_ip_rps": {
                    "type": "integer"
                  },
                  "task_status_poll_per_key_per_min": {
                    "type": "integer"
                  }
                }
              },
              "credits_per_call": {
                "type": "object",
                "additionalProperties": true
              },
              "user_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "OpenAPIUploadResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "upload_id": {
                "type": "string",
                "description": "Opaque upload handle. This is not a public URL."
              },
              "filename": {
                "type": "string"
              },
              "size": {
                "type": "integer"
              },
              "content_type": {
                "type": "string"
              },
              "expires_at": {
                "type": "integer",
                "description": "Unix seconds when the upload handle expires."
              }
            }
          }
        }
      },
      "OpenAPIEstimateRequest": {
        "type": "object",
        "required": [
          "operation",
          "input"
        ],
        "properties": {
          "operation": {
            "$ref": "#/components/schemas/OpenAPITaskOperation"
          },
          "input": {
            "type": "object",
            "additionalProperties": true,
            "description": "Operation-specific input. For pdf_to_ppt, pass either upload_id or a caller-hosted pdf_url."
          }
        }
      },
      "OpenAPIEstimateResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "message": {
            "type": "string",
            "example": "ok"
          },
          "data": {
            "type": "object",
            "properties": {
              "operation": {
                "$ref": "#/components/schemas/OpenAPITaskOperation"
              },
              "credits": {
                "type": "number"
              },
              "page_count": {
                "type": "integer"
              },
              "unit": {
                "type": "string"
              },
              "available_credits": {
                "type": "number"
              },
              "sufficient": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "responses": {
      "ImageUrlListSuccess": {
        "description": "Processed image URLs",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ImageUrlListResponse"
            },
            "example": {
              "code": 0,
              "message": "ok",
              "data": {
                "image_urls": [
                  "https://cdn.codia.ai/images/result.png"
                ]
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "The request body is malformed or missing required parameters",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 400,
              "message": "Bad Request",
              "data": null
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing, invalid, or revoked",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 401,
              "message": "Unauthorized",
              "data": null
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Account balance is insufficient or subscription has expired",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 402,
              "message": "Payment Required",
              "data": null
            }
          }
        }
      },
      "Forbidden": {
        "description": "Account does not have access to this endpoint",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 403,
              "message": "Forbidden",
              "data": null
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with an existing resource or idempotency key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 409,
              "message": "Conflict",
              "data": null
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded for your subscription tier",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 429,
              "message": "Too Many Requests",
              "data": null
            }
          }
        },
        "headers": {
          "Retry-After": {
            "description": "Number of whole seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "required": true
          }
        }
      },
      "InternalError": {
        "description": "An unexpected error occurred on the server",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "code": 500,
              "message": "Internal Server Error",
              "data": null
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "taskStatusChanged": {
      "post": {
        "summary": "Codia task webhook",
        "description": "Codia POSTs this event to your `callback_url` when a task reaches `succeeded`, `failed`, or `canceled`. See the Webhook callback section of POST /v2/open/tasks for signature verification and retry behaviour.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "task_id": "550e8400-e29b-41d4-a716-446655440000",
                "operation": "pdf_to_ppt",
                "status": "succeeded",
                "timestamp": 1764000088,
                "result": {
                  "ppt_url": "https://cdn.codia.ai/pptx/q4-report.pptx"
                },
                "error": ""
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-Codia-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "HMAC-SHA256 of the raw request body, signed with your API key (right-padded to 32 bytes with NUL if shorter). Format: `sha256=<hex>`."
          }
        ],
        "responses": {
          "200": {
            "description": "Receiver acknowledged the event. Any 2xx status is accepted."
          }
        }
      }
    }
  }
}
