{
  "openapi": "3.1.0",
  "info": {
    "title": "Peeka API",
    "version": "1.1.0",
    "summary": "Turn one photo and one line into a short cinematic clip with sound.",
    "description": "REST API for Peeka (https://peekaai.com). Create video clips from a text description and optional reference photos, poll for the result or receive a signed webhook.\n\n**Auth.** `Authorization: Bearer pk_live_…`. Create keys in Account → API (needs a balance of at least 240 credits to unlock; a key keeps working while the balance is at least 10).\n\n**Prices.** Credits per second: standard 480p 2, HD 720p 4, Full HD 1080p 10. The API gets 10% off: cost = credits per second × seconds × 0.9, rounded up (a 5-second standard clip is 9 credits). Live prices: `GET /pricing`. Failed generations are refunded automatically.\n\n**Retries.** Send an `Idempotency-Key` header on `POST /videos` and `POST /videos/batch`. The same key with the same body within 24 hours returns the original response (header `Idempotent-Replayed: true`) and never charges twice.\n\n**Faces.** Reference photos with a real person need `subject_consent`. External `image_urls` are downloaded and moderated by Peeka before anything is generated; photos that fail moderation return `422 photo_rejected` and nothing is charged.\n\n**Errors.** Every error is `application/problem+json` (RFC 9457) with `code`, `retryable`, `retry_after` (seconds) and, when a person must act, `user_action {kind, url}`. The legacy `error` string is kept.\n\n**Assistants.** Claude, ChatGPT and other MCP clients should use the remote MCP server at https://peekaai.com/mcp (OAuth sign-in, per-assistant daily limit, app prices) instead of this API. See https://peekaai.com/connect.",
    "contact": {
      "name": "Peeka",
      "email": "contact@peekaai.com",
      "url": "https://peekaai.com/api-docs"
    },
    "termsOfService": "https://peekaai.com/terms"
  },
  "externalDocs": {
    "description": "Plain-text docs",
    "url": "https://peekaai.com/api-docs.md"
  },
  "servers": [
    {
      "url": "https://peekaai.com/api/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Videos",
      "description": "Create, poll, list, cancel and retry clips."
    },
    {
      "name": "Images",
      "description": "Still images: from a description, a template, photo edits, or an AI character that can star in clips."
    },
    {
      "name": "Assets",
      "description": "Upload reference photos once and reuse them."
    },
    {
      "name": "Account",
      "description": "Balance and usage."
    },
    {
      "name": "Public",
      "description": "No key needed."
    }
  ],
  "paths": {
    "/videos": {
      "post": {
        "tags": [
          "Videos"
        ],
        "operationId": "createVideo",
        "summary": "Create a clip",
        "description": "Charges the credits up front (refunded automatically if generation fails) and starts generation. Typical time is 1-2 minutes. Poll `poll_url` or use `callback_url`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVideoRequest"
              },
              "examples": {
                "text": {
                  "summary": "Text only",
                  "value": {
                    "prompt": "A slow drone shot rising over Dubai Marina at sunset",
                    "duration": 5,
                    "ratio": "9:16",
                    "model": "standard"
                  }
                },
                "photo": {
                  "summary": "From an uploaded asset",
                  "value": {
                    "prompt": "She turns to the camera and smiles, golden hour",
                    "asset_ids": [
                      1234
                    ],
                    "subject_consent": "self",
                    "duration": 5,
                    "ratio": "9:16"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. Generation started.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per minute for this key (60)."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time (seconds) when the window resets."
              },
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "Present when this is a replay of an earlier request with the same Idempotency-Key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateVideoResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "409": {
            "description": "idempotency_in_progress: the first request with this key is still running. Retry after retry_after seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "consent_required (image_urls without subject_consent), photo_rejected (moderation), image_fetch_failed, or idempotency_key_reused (same key, different body).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "upstream_busy, moderation_pending (photo still being checked; retry), or generation_paused. Nothing was charged.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Videos"
        ],
        "operationId": "listVideos",
        "summary": "List clips",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "generating",
                "done",
                "error",
                "cancelled"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Clips, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/videos/{id}": {
      "get": {
        "tags": [
          "Videos"
        ],
        "operationId": "getVideo",
        "summary": "Get a clip",
        "description": "Poll every 5-10 seconds until `status` is `done` or `error`. When done, `video_url` is an absolute signed link valid for 7 days (no key needed to open it); call this endpoint again for a fresh link.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The clip.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Video"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Videos"
        ],
        "operationId": "deleteVideo",
        "summary": "Cancel or delete a clip",
        "description": "A clip that is still queued is cancelled and its credits are refunded. A clip that is already rendering can no longer be stopped (the model is already making it): you get 409 `already_rendering`, and it finishes and is charged normally. A finished, failed or cancelled clip is deleted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteVideoResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "already_rendering: the clip is already being made and can no longer be cancelled; it will finish shortly.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/videos/{id}/retry": {
      "post": {
        "tags": [
          "Videos"
        ],
        "operationId": "retryVideo",
        "summary": "Run a failed clip again",
        "description": "Only for tasks that failed with upstream_busy, upstream_unavailable, upstream_timeout or upstream_error. Once per task, never for a retry. Replays the original parameters and is charged like a new request at the API price (the failed task was already refunded). If the retry fails too, it is refunded automatically.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Retry started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetryResponse"
                }
              }
            }
          },
          "400": {
            "description": "not_failed, not_retryable or cannot_retry_a_retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "already_retried, or retry_in_progress (a retry for this clip is being started).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "consent_required: the task used unmoderated external image URLs; submit a new request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "upstream_busy: the credits service could not be reached; nothing was charged.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/videos/batch": {
      "post": {
        "tags": [
          "Videos"
        ],
        "operationId": "createVideoBatch",
        "summary": "Create up to 5 clips at once",
        "description": "Every item is validated (and its photos moderated) first; if any item fails, nothing is created or charged. Then the total is charged in one step. Returns 207 even when all items succeed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          }
        },
        "responses": {
          "207": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "409": {
            "description": "idempotency_in_progress.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "An item needs subject_consent, a photo was rejected, or the Idempotency-Key was reused with a different body. `item_index` says which item.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "upstream_busy or moderation_pending. Nothing was charged.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/images": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "createImage",
        "summary": "Make images",
        "description": "Reserves the price and starts generation (usually 30-60 s; an AI character about 2 min). Prices: standard 2 credits (4 variations 7), hd 4 (4 variations 14), AI character 5. Images that cannot be made are refunded when the job ends.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateImageRequest"
              },
              "examples": {
                "text": {
                  "summary": "From a description",
                  "value": {
                    "description": "A cup of Arabic coffee and dates on a wooden table, soft morning light",
                    "aspect_ratio": "4:5",
                    "count": 4
                  }
                },
                "card": {
                  "summary": "Eid card template",
                  "value": {
                    "template": "eid",
                    "fields": {
                      "style": "emerald_gold",
                      "greeting": "Eid Mubarak"
                    }
                  }
                },
                "product": {
                  "summary": "Product photo from an uploaded asset",
                  "value": {
                    "template": "product",
                    "fields": {
                      "scene": "marble"
                    },
                    "asset_ids": [
                      1234
                    ],
                    "subject_consent": "no_real_person"
                  }
                },
                "character": {
                  "summary": "AI character",
                  "value": {
                    "type": "character",
                    "description": "short curly hair, gold hoop earrings, mustard linen shirt",
                    "character": {
                      "age": "26-35",
                      "style": "photo"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. Generation started.",
            "headers": {
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "400": {
            "description": "missing_input, preset_not_found, field_required, photos_required, too_many_images, character_no_photos, invalid_asset_id, invalid_image_url, invalid_idempotency_key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "409": {
            "description": "idempotency_in_progress.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "consent_required, prompt_blocked, document_blocked, religious_text, minor_blocked, text_script_unsupported, cost_exceeds_max, photo_rejected, image_fetch_failed, idempotency_key_reused.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "rate_limit_exceeded (per key), concurrency_limit (3 image jobs at a time) or rate_limited (30 jobs per 10 minutes).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "upstream_busy, moderation_pending or generation_paused. Nothing was charged.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Images"
        ],
        "operationId": "listImages",
        "summary": "List image jobs",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Job id cursor from next_before."
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJobList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/images/{id}": {
      "get": {
        "tags": [
          "Images"
        ],
        "operationId": "getImage",
        "summary": "Get an image job",
        "description": "With `wait`, the call returns as soon as the job ends or after `wait` seconds.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "wait",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 30,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job and its images.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageJob"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Images"
        ],
        "operationId": "deleteImage",
        "summary": "Delete the images of a job",
        "description": "The image links stop working. Credits are not refunded.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "deleted": {
                      "type": "boolean"
                    },
                    "images_deleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/assets": {
      "post": {
        "tags": [
          "Assets"
        ],
        "operationId": "uploadAsset",
        "summary": "Upload a reference photo",
        "description": "Runs Peeka moderation (5-60 seconds, asynchronous). If `status` is `pending`, poll GET /assets/{id} until `trusted`, then pass the id in `asset_ids`. Identical files are deduplicated. 0 credits.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "contentMediaType": "application/octet-stream",
                    "description": "jpg, png, webp, gif or heic; up to 20 MB."
                  },
                  "subject_consent": {
                    "type": "string",
                    "enum": [
                      "self",
                      "permission",
                      "no_real_person"
                    ],
                    "description": "Who is in the reference photos. \"self\": the person is you (the account holder or the end user you act for, per the API terms). \"permission\": the person agreed. \"no_real_person\": objects, pets, artwork. Required with image_urls; recommended with asset_ids. \"third_party_with_permission\" is accepted as an alias of \"permission\"."
                  },
                  "sha256": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$",
                    "description": "Optional client-computed hash for deduplication."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored (maybe still being checked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetUploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "no_file, bad_mime or invalid subject_consent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "413": {
            "description": "too_large.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "moderation_rejected.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "Assets"
        ],
        "operationId": "listAssets",
        "summary": "List moderated photos",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trusted image assets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/assets/{id}": {
      "get": {
        "tags": [
          "Assets"
        ],
        "operationId": "getAsset",
        "summary": "Get a photo and its moderation status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Assets"
        ],
        "operationId": "deleteAsset",
        "summary": "Delete a photo",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Balance and API usage",
        "responses": {
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/pricing": {
      "get": {
        "tags": [
          "Public"
        ],
        "operationId": "getPricing",
        "summary": "Current prices (no key needed)",
        "security": [],
        "responses": {
          "200": {
            "description": "Prices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                }
              }
            }
          }
        }
      }
    },
    "/media/{file}": {
      "get": {
        "tags": [
          "Public"
        ],
        "operationId": "getSignedMedia",
        "summary": "Open a signed video or poster link",
        "description": "The links in `video_url` and `poster_url`. The signature is the credential; links expire after 7 days and stop working once the clip is deleted or moved to Recently deleted. Supports HTTP Range.",
        "security": [],
        "parameters": [
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+\\.(mp4|jpg)$"
            }
          },
          {
            "name": "exp",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sig",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "download",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Ask the browser to download instead of play."
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "video/mp4"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "image/jpeg"
                }
              }
            }
          },
          "206": {
            "description": "Partial content (Range request)."
          },
          "302": {
            "description": "Redirect to a short-lived storage URL."
          },
          "403": {
            "description": "invalid_signature.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "not_found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "media_not_ready: the clip finished and is still being saved; retry in a few seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "410": {
            "description": "link_expired: fetch the clip again for a fresh link.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "videoFinished": {
      "post": {
        "operationId": "videoFinishedWebhook",
        "summary": "A clip reached a terminal state",
        "description": "Sent to `callback_url` when a clip is done or failed. Headers: `X-Peeka-Event` (video.completed | video.failed), `X-Peeka-Signature` (`sha256=` + hex HMAC-SHA256 of the raw body with your webhook secret), `X-Peeka-Delivery` (unique per delivery; use it to deduplicate). Reply 2xx. Non-2xx is retried after 30 s, 2 min, 10 min, 30 min and 2 h; 4xx is not retried. Note: the `video_url` in the payload opens for a signed-in browser; call GET /videos/{id} for a signed link that works anywhere.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pk_live_…",
        "description": "Peeka API key."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7E]+$"
        },
        "description": "Any unique string (e.g. a UUID). Same key + same body within 24 hours → the original response, charged once. Same key + different body → 422 idempotency_key_reused."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid input: missing_input, invalid_duration, invalid_ratio, invalid_model, invalid_resolution, resolution_model_mismatch, too_many_images, invalid_image_url, invalid_callback_url, invalid_asset_id, batch_too_large, invalid_idempotency_key, invalid_json.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "invalid_auth_header or invalid_api_key.",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "insufficient_credits. `required` and `balance` are included; `user_action.url` is the top-up page. Also returned by every endpoint when the balance is below 10.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "not_found.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "rate_limit_exceeded (60 requests per minute per key).",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "InternalError": {
        "description": "internal_error or task_create_failed. Credits are refunded; retry with the same Idempotency-Key.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details with Peeka extensions. Extra members depend on the code (e.g. required/balance, cost, item_index, asset_ids, reason_code).",
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "example": "https://peekaai.com/api-docs#error-insufficient_credits"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code."
          },
          "error": {
            "type": "string",
            "description": "Legacy human-readable message (kept for older clients)."
          },
          "retryable": {
            "type": "boolean",
            "description": "True when the same request can succeed later without changes."
          },
          "retry_after": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Seconds to wait before retrying. Also sent as the Retry-After header."
          },
          "user_action": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "top_up",
                      "confirm_consent",
                      "raise_budget",
                      "upload_photo",
                      "contact_support"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            ]
          },
          "trace_id": {
            "type": "string",
            "description": "Quote this to support."
          }
        },
        "additionalProperties": true
      },
      "CreateVideoRequest": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string",
            "description": "What should happen in the clip. Write in English for best results. Required unless reference images are given."
          },
          "duration": {
            "type": "integer",
            "minimum": 4,
            "maximum": 30,
            "default": 5,
            "description": "Length in whole seconds: 4–15 for standard models, 4–30 for cinema models."
          },
          "ratio": {
            "type": "string",
            "enum": [
              "16:9",
              "9:16",
              "1:1",
              "4:3",
              "3:4",
              "21:9"
            ],
            "default": "16:9"
          },
          "aspect_ratio": {
            "type": "string",
            "enum": [
              "16:9",
              "9:16",
              "1:1",
              "4:3",
              "3:4",
              "21:9"
            ],
            "description": "Alias of ratio."
          },
          "model": {
            "type": "string",
            "enum": [
              "standard",
              "standard-hd",
              "standard-fullhd",
              "cinema",
              "cinema-hd",
              "cinema-fullhd"
            ],
            "default": "standard",
            "description": "Engine and resolution. standard = 480p, standard-hd = 720p, standard-fullhd = 1080p (4–15 s). cinema, cinema-hd, cinema-fullhd = our newest model: more natural motion, 4–30 s, and characters can speak (including Arabic, with lip sync). Model IDs used by earlier API versions are still accepted."
          },
          "resolution": {
            "type": "string",
            "enum": [
              "480p",
              "720p"
            ],
            "description": "Optional; must match the model tier (720p needs a -vip model)."
          },
          "image_urls": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Public image URLs (http/https) or your own moderated asset:// URIs. Peeka downloads each image, runs the same moderation as the web upload and generates from the moderated copy. Needs subject_consent. Prefer asset_ids."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "deprecated": true,
            "description": "Single-image form of image_urls."
          },
          "subject_consent": {
            "type": "string",
            "enum": [
              "self",
              "permission",
              "no_real_person"
            ],
            "description": "Who is in the reference photos. \"self\": the person is you (the account holder or the end user you act for, per the API terms). \"permission\": the person agreed. \"no_real_person\": objects, pets, artwork. Required with image_urls; recommended with asset_ids. \"third_party_with_permission\" is accepted as an alias of \"permission\"."
          },
          "asset_ids": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "integer"
            },
            "description": "IDs from POST /assets (must be yours and moderated). Takes priority over image_urls."
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "HTTPS URL that receives a signed POST when the task finishes (see webhooks)."
          }
        }
      },
      "CreateVideoResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "credits_used": {
            "type": "integer"
          },
          "duration": {
            "type": "integer"
          },
          "ratio": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "image_urls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The image_urls you sent (generation used the moderated copies)."
          },
          "asset_ids_used": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          },
          "asset_source": {
            "type": "string",
            "enum": [
              "asset_ids",
              "image_urls",
              "default_asset_ids",
              "none"
            ]
          },
          "subject_consent": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "self",
              "permission",
              "no_real_person",
              null
            ]
          },
          "callback_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "callback_enabled": {
            "type": "boolean"
          },
          "estimated_time_seconds": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "poll_url": {
            "type": "string",
            "format": "uri"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Video": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "generating",
              "done",
              "error",
              "cancelled"
            ]
          },
          "prompt": {
            "type": "string"
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Absolute signed link, valid 7 days. Null until done."
          },
          "poster_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Signed JPEG frame, valid 7 days."
          },
          "share_page_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Public page, only if the owner shared the clip on Peeka."
          },
          "credits_used": {
            "type": "integer",
            "description": "0 after a refund."
          },
          "refunded": {
            "type": "boolean"
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration": {
            "type": [
              "integer",
              "null"
            ]
          },
          "ratio": {
            "type": [
              "string",
              "null"
            ]
          },
          "progress": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short plain sentence for people. Use error_code in code; the text can change."
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "image_fetch_failed, content_rejected, copyright_rejected, output_rejected, real_person_rejected, task_config_error, upstream_busy, upstream_unavailable, upstream_timeout, upstream_auth_failed, upstream_bad_request, upstream_error, cancelled."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "poll_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "VideoList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Video"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "DeleteVideoResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "action": {
            "type": "string",
            "enum": [
              "cancelled",
              "deleted"
            ]
          },
          "id": {
            "type": "integer"
          },
          "credits_refunded": {
            "type": "integer"
          }
        }
      },
      "RetryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "retry_of_task_id": {
            "type": "integer"
          },
          "credits_used": {
            "type": "integer",
            "description": "Credits charged for the retry (same as a new request at the API price)."
          },
          "free_retry": {
            "type": "boolean",
            "description": "Always false; kept for older clients."
          },
          "prompt": {
            "type": "string"
          },
          "model": {
            "type": "string",
            "description": "standard, standard-hd, standard-fullhd, cinema, cinema-hd or cinema-fullhd."
          },
          "duration": {
            "type": "integer"
          },
          "ratio": {
            "type": "string"
          },
          "callback_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "callback_enabled": {
            "type": "boolean"
          },
          "estimated_time_seconds": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "poll_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/CreateVideoRequest"
            }
          },
          "defaults": {
            "$ref": "#/components/schemas/CreateVideoRequest",
            "description": "Applied to any item that omits a field."
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string"
          },
          "total_credits_used": {
            "type": "integer"
          },
          "items_count": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "ok": {
                  "type": "boolean"
                },
                "id": {
                  "type": "integer"
                },
                "status": {
                  "type": "string"
                },
                "credits_used": {
                  "type": "integer"
                },
                "prompt": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "duration": {
                  "type": "integer"
                },
                "ratio": {
                  "type": "string"
                },
                "asset_source": {
                  "type": "string"
                },
                "asset_ids_used": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "integer"
                  }
                },
                "subject_consent": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "callback_enabled": {
                  "type": "boolean"
                },
                "estimated_time_seconds": {
                  "type": "integer"
                },
                "poll_url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AssetUploadResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "trusted"
            ]
          },
          "asset_uri": {
            "type": [
              "string",
              "null"
            ]
          },
          "public_url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string"
          },
          "file_size": {
            "type": "integer"
          },
          "mime_type": {
            "type": "string"
          },
          "trust_level": {
            "type": "string"
          },
          "subject_consent": {
            "type": [
              "string",
              "null"
            ]
          },
          "deduped": {
            "type": "boolean"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Asset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "trusted",
              "rejected"
            ]
          },
          "public_url": {
            "type": "string"
          },
          "asset_uri": {
            "type": [
              "string",
              "null"
            ]
          },
          "rejection_note": {
            "type": "string"
          }
        }
      },
      "AssetList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "asset_uri": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "public_url": {
                  "type": "string"
                },
                "file_size": {
                  "type": "integer"
                },
                "mime_type": {
                  "type": "string"
                },
                "trust_level": {
                  "type": "string"
                },
                "asset_category": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "credits": {
            "type": "integer"
          },
          "total_spent_aed": {
            "type": "number"
          },
          "api_usage": {
            "type": "object",
            "properties": {
              "today": {
                "type": "object",
                "properties": {
                  "requests": {
                    "type": "integer"
                  },
                  "creditsUsed": {
                    "type": "integer"
                  }
                }
              },
              "total": {
                "type": "object",
                "properties": {
                  "requests": {
                    "type": "integer"
                  },
                  "creditsUsed": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "member_since": {
            "type": [
              "string",
              "null"
            ]
          },
          "default_asset_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "topup_url": {
            "type": "string",
            "format": "uri"
          },
          "pricing_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "unit": {
            "type": "string",
            "enum": [
              "credits"
            ]
          },
          "api_discount_percent": {
            "type": "integer",
            "example": 10
          },
          "api_cost_rule": {
            "type": "string"
          },
          "tiers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tier": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "credits_per_second_web": {
                  "type": "number"
                },
                "api_cost_examples": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer"
                  }
                },
                "min_seconds": {
                  "type": "integer",
                  "example": 4
                },
                "max_seconds": {
                  "type": "integer",
                  "description": "15 for standard models, 30 for cinema models."
                }
              }
            }
          },
          "durations_note": {
            "type": "string",
            "example": "Standard 4–15 s, Cinema 4–30 s (any whole second)."
          },
          "allowed_durations": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Standard models: 4–15."
          },
          "allowed_durations_cinema": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Cinema models: 4–30."
          },
          "allowed_ratios": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "models": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "standard",
                "standard-hd",
                "standard-fullhd",
                "cinema",
                "cinema-hd",
                "cinema-fullhd"
              ]
            }
          },
          "packs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pack": {
                  "type": "string"
                },
                "aed": {
                  "type": "number"
                },
                "usd": {
                  "type": "number"
                },
                "credits": {
                  "type": "integer"
                }
              }
            }
          },
          "first_topup_bonus": {
            "type": "object",
            "properties": {
              "rate": {
                "type": "number"
              },
              "cap": {
                "type": "integer"
              }
            }
          },
          "topup_url": {
            "type": "string",
            "format": "uri"
          },
          "assistants_note": {
            "type": "string"
          }
        },
        "example": {
          "unit": "credits",
          "api_discount_percent": 10,
          "api_cost_rule": "credits per second x seconds x 0.9, rounded up",
          "tiers": [
            {
              "tier": "standard_480p",
              "model": "standard",
              "credits_per_second_web": 2,
              "api_cost_examples": {
                "5s": 9,
                "10s": 18,
                "15s": 27
              }
            },
            {
              "tier": "standard_hd_720p",
              "model": "standard-hd",
              "credits_per_second_web": 4,
              "api_cost_examples": {
                "5s": 18,
                "10s": 36,
                "15s": 54
              }
            },
            {
              "tier": "standard_full_hd_1080p",
              "model": "standard-fullhd",
              "credits_per_second_web": 10,
              "api_cost_examples": {
                "5s": 45,
                "10s": 90,
                "15s": 135
              }
            }
          ]
        }
      },
      "WebhookPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "video.completed",
              "video.failed"
            ]
          },
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "credits_used": {
            "type": "integer"
          },
          "duration": {
            "type": [
              "integer",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateImageRequest": {
        "type": "object",
        "description": "Send `description` or `template` (or both). Up to 3 photos via `asset_ids` or `image_urls` (with `subject_consent`). For `type: character`, describe a made-up adult in `description`; no photos.",
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 1000,
            "description": "What to make, or for photo edits what to change. English, Arabic or Chinese. Alias: `prompt`."
          },
          "type": {
            "type": "string",
            "enum": [
              "image",
              "character"
            ],
            "default": "image",
            "description": "`character` = 4 photos of one made-up adult; usable in clips for 30 days."
          },
          "template": {
            "type": "string",
            "enum": [
              "eid",
              "diwali",
              "national_day",
              "festive",
              "celebration",
              "headshot",
              "product",
              "poster"
            ]
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Template fields, e.g. {\"style\": \"emerald_gold\", \"greeting\": \"Eid Mubarak\"}. See the API docs for each template's fields."
          },
          "text": {
            "type": "string",
            "maxLength": 60,
            "description": "Short text drawn into the image by AI. Spelling is not guaranteed."
          },
          "aspect_ratio": {
            "type": "string",
            "enum": [
              "1:1",
              "9:16",
              "4:5",
              "16:9",
              "3:4"
            ],
            "default": "1:1"
          },
          "quality": {
            "type": "string",
            "enum": [
              "standard",
              "hd"
            ],
            "default": "standard"
          },
          "count": {
            "type": "integer",
            "enum": [
              1,
              4
            ],
            "default": 1
          },
          "character": {
            "type": "object",
            "properties": {
              "age": {
                "type": "string",
                "enum": [
                  "18-25",
                  "26-35",
                  "36-50",
                  "51+"
                ]
              },
              "style": {
                "type": "string",
                "enum": [
                  "photo",
                  "cinematic",
                  "animated",
                  "illustrated"
                ]
              }
            }
          },
          "asset_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "maxItems": 3
          },
          "image_urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "maxItems": 3
          },
          "subject_consent": {
            "type": "string",
            "enum": [
              "self",
              "permission",
              "no_real_person"
            ]
          },
          "max_credits": {
            "type": "integer",
            "minimum": 1,
            "description": "Refuse with 422 cost_exceeds_max instead of charging more."
          }
        }
      },
      "ImageOutput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Permanent, unguessable link to the original file (PNG with text, otherwise JPEG; original metadata kept). Stops working when deleted."
          },
          "thumbnail_url": {
            "type": "string",
            "format": "uri"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "format": {
            "type": "string",
            "enum": [
              "jpeg",
              "png"
            ]
          },
          "animate": {
            "type": "string",
            "enum": [
              "trusted",
              "ok",
              "maybe",
              "no"
            ],
            "description": "trusted = AI character, works in clips until trusted_until; ok = can become a clip (no people, or made from the user's own photos of people who agreed); maybe = may show people, a clip may be refused; no = edited from a photo of a real person while such images can't become clips, stays an image."
          },
          "trusted_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ImageJob": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "image_job"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "generating",
              "done",
              "partial",
              "failed"
            ],
            "description": "partial = some images were made; the rest was refunded."
          },
          "type": {
            "type": "string",
            "enum": [
              "image",
              "character"
            ]
          },
          "template": {
            "type": [
              "string",
              "null"
            ]
          },
          "quality": {
            "type": "string",
            "enum": [
              "standard",
              "hd"
            ]
          },
          "aspect_ratio": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "done": {
            "type": "integer"
          },
          "description": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "credits_reserved": {
            "type": "integer"
          },
          "credits_charged": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Final charge once the job has ended; null while generating."
          },
          "credits_refunded": {
            "type": "integer"
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "prompt_blocked, upload_blocked, result_blocked, copyright_blocked, real_person_blocked, document_blocked, busy, timeout, failed."
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageOutput"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "estimated_time_seconds": {
            "type": "integer",
            "description": "Only in the 202 response."
          },
          "poll_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ImageJobList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImageJob"
            }
          },
          "next_before": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pass as `before` for the next page."
          },
          "has_more": {
            "type": "boolean"
          }
        }
      }
    }
  }
}
