{
  "openapi": "3.1.0",
  "info": {
    "title": "Reviral Media API",
    "version": "1.0.0",
    "description": "Create an image or video with a currently listed model. The model catalog and credit prices are dynamic. Reference inputs are not available in v1.",
    "contact": {
      "email": "support@reviral.ai",
      "url": "https://reviral.ai"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://reviral.ai/terms"
    }
  },
  "servers": [
    {
      "url": "https://reviral.ai",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/v1/models": {
      "get": {
        "summary": "List available models",
        "description": "Returns current visible, priced models and live prices. Limited to 60 requests per minute per trusted platform client address, or one global bucket when unavailable.",
        "responses": {
          "200": {
            "description": "Current dynamic model catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/generate-media": {
      "get": {
        "summary": "Describe the generation contract",
        "responses": {
          "200": {
            "description": "Human-readable field and polling summary"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "summary": "Start a media job",
        "description": "Limited to 10 requests per minute and 200 per day per key. Video also has an account cap: 10 per hour and 100 per day until the account has paid history; 200 per day after. Reference fields return 422 unsupported_field. Image credits are per image and count multiplies the charge.",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateMediaRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted or the original job returned when the same Idempotency-Key and request body are repeated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptedJobResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadJson"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/GenerationFailed"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "callbacks": {
          "jobTerminal": {
            "{$request.body#/webhookUrl}": {
              "post": {
                "summary": "Best-effort terminal callback",
                "description": "Sent at most once with no retries. Delivery failure does not change the job.",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/WebhookPayload"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Callback received"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/render": {
      "get": {
        "summary": "Describe the full render contract",
        "description": "Returns the current field, workflow, pricing, rate-limit, and response summary for the full short-form video endpoint.",
        "responses": {
          "200": {
            "description": "Human-readable full render contract"
          }
        }
      },
      "post": {
        "summary": "Start a full short-form video render",
        "description": "Starts a complete video workflow with script, voice, scenes, captions, and optional music.",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Render accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderResponse"
                }
              }
            }
          },
          "400": {
            "description": "Request validation failed (forwarded from the render pipeline)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderPipelineError"
                }
              }
            }
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Account needs more credits (forwarded from the render pipeline)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderPipelineError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (forwarded from the render pipeline)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderPipelineError"
                }
              }
            }
          },
          "500": {
            "description": "The render could not be started (forwarded from the render pipeline; a rarer auth-layer failure instead returns LegacyErrorEnvelope)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderPipelineError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "summary": "Get job status",
        "description": "Limited to 120 requests per minute per key. Image creditsCharged is net of refunds.",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Owned API job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/JobNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/StatusFailed"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/calculate-credits": {
      "get": {
        "summary": "Describe the credit estimate contract",
        "security": [],
        "responses": {
          "200": {
            "description": "Credit estimate contract"
          }
        }
      },
      "post": {
        "summary": "Estimate full-render credits",
        "description": "Validates and estimates a full render without authentication, a database write, or a credit debit.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalculateCreditsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimated credits and customer-safe breakdown"
          },
          "400": {
            "description": "Invalid body"
          }
        }
      }
    },
    "/api/v1/credit-costs": {
      "get": {
        "summary": "Get the credit cost reference",
        "security": [],
        "responses": {
          "200": {
            "description": "Current credit rate card"
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Get the authenticated account",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account profile, plan, and credit balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Profile not found"
          },
          "502": {
            "description": "Account lookup unavailable"
          }
        }
      }
    },
    "/api/v1/characters": {
      "get": {
        "summary": "List saved characters",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "responses": {
          "200": {
            "description": "Owned active characters"
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Character list unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a character",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CharacterRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Character created"
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Character save unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "501": {
            "description": "Requested character source is not implemented",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/characters/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "summary": "Get one saved character",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "responses": {
          "200": {
            "description": "Owned active character"
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Character not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Character lookup unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Soft-delete a saved character",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "responses": {
          "200": {
            "description": "Character deleted"
          },
          "400": {
            "description": "Invalid character ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Character delete unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/media-search": {
      "get": {
        "summary": "Describe media search",
        "security": [],
        "responses": {
          "200": {
            "description": "Media search contract"
          }
        }
      },
      "post": {
        "summary": "Search available media",
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyAuthorization": []
          },
          {
            "ApiKeyXReviral": []
          },
          {
            "ApiKeyShort": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaSearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked matching media"
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Key is missing, revoked, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key in x-api-key header."
      },
      "ApiKeyAuthorization": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "rvr_<hex>",
        "description": "API key in Authorization: Bearer header."
      },
      "ApiKeyXReviral": {
        "type": "apiKey",
        "in": "header",
        "name": "x-reviral-api-key",
        "description": "API key in x-reviral-api-key header."
      },
      "ApiKeyShort": {
        "type": "apiKey",
        "in": "header",
        "name": "key",
        "description": "API key in key header."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "1-128 characters. Reuse the same value only with the same request body. A different body returns 409 idempotency_key_reuse.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      }
    },
    "schemas": {
      "DurationRange": {
        "type": "object",
        "required": [
          "kind",
          "min",
          "max"
        ],
        "properties": {
          "kind": {
            "const": "range"
          },
          "min": {
            "type": "integer"
          },
          "max": {
            "type": "integer"
          }
        }
      },
      "DurationEnum": {
        "type": "object",
        "required": [
          "kind",
          "values"
        ],
        "properties": {
          "kind": {
            "const": "enum"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "CreditPrice": {
        "type": "object",
        "properties": {
          "perSecond": {
            "type": "number",
            "description": "Published to four decimal places."
          },
          "creditsAtDefaultDuration": {
            "type": "integer",
            "description": "Real rounded charge at the model default duration."
          },
          "perVideo": {
            "type": "integer"
          },
          "perImage": {
            "type": "integer",
            "description": "Credits per image; count multiplies."
          },
          "byDuration": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "ModelDefaults": {
        "type": "object",
        "required": [
          "resolution",
          "aspectRatio"
        ],
        "properties": {
          "durationSec": {
            "type": "integer"
          },
          "resolution": {
            "type": "string"
          },
          "aspectRatio": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "Model": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "label",
          "family",
          "maker",
          "billing",
          "durations",
          "resolutions",
          "aspects",
          "credits",
          "defaults"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable public alias."
          },
          "kind": {
            "enum": [
              "video",
              "image"
            ]
          },
          "label": {
            "type": "string"
          },
          "family": {
            "type": "string"
          },
          "maker": {
            "type": "string"
          },
          "billing": {
            "enum": [
              "per_second",
              "per_request"
            ]
          },
          "durations": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DurationRange"
              },
              {
                "$ref": "#/components/schemas/DurationEnum"
              }
            ]
          },
          "resolutions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "aspects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "credits": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/CreditPrice"
            }
          },
          "defaults": {
            "$ref": "#/components/schemas/ModelDefaults"
          }
        }
      },
      "ModelsResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "Dynamic; only models available at request time.",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "GenerateMediaRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "model",
          "prompt"
        ],
        "description": "Reference image, video, and audio fields are unsupported in v1.",
        "properties": {
          "model": {
            "type": "string"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100000
          },
          "durationSec": {
            "type": "integer",
            "minimum": 1
          },
          "resolution": {
            "type": "string"
          },
          "aspectRatio": {
            "type": "string"
          },
          "generateAudio": {
            "type": "boolean"
          },
          "size": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "description": "Image count; multiplies perImage credits."
          },
          "maxCredits": {
            "type": "integer",
            "minimum": 0,
            "description": "Price lock. A different live charge returns 409 price_changed."
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "RenderRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "workflow"
        ],
        "properties": {
          "workflow": {
            "type": "string",
            "enum": [
              "prompt-to-video",
              "script-to-video",
              "article-to-video",
              "static-bg-video",
              "caption-video",
              "ad-generator",
              "ugc-ad",
              "avatar-to-video",
              "quiz-video"
            ]
          },
          "prompt": {
            "type": "string",
            "maxLength": 100000
          },
          "userScript": {
            "type": "string",
            "maxLength": 4000
          },
          "articleUrl": {
            "type": "string",
            "format": "uri"
          },
          "videoUrl": {
            "type": "string",
            "format": "uri"
          },
          "productUrl": {
            "type": "string",
            "format": "uri"
          },
          "avatarImageUrl": {
            "type": "string",
            "format": "uri"
          },
          "tier": {
            "type": "string",
            "enum": [
              "base",
              "pro",
              "ultra"
            ],
            "default": "pro"
          },
          "durationSec": {
            "type": "integer",
            "minimum": 15,
            "maximum": 300,
            "default": 30
          },
          "aspectRatio": {
            "type": "string",
            "enum": [
              "9:16",
              "16:9",
              "1:1",
              "auto"
            ],
            "default": "9:16"
          },
          "presetId": {
            "type": "string",
            "default": "stock"
          },
          "language": {
            "type": "string",
            "default": "en"
          },
          "captionStyle": {
            "type": "string",
            "default": "highlight"
          },
          "captionPosition": {
            "type": "string",
            "enum": [
              "top",
              "middle",
              "bottom"
            ],
            "default": "bottom"
          },
          "music": {
            "type": "boolean",
            "default": true
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri"
          },
          "nbGenerations": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          },
          "musicUrl": {
            "type": "string",
            "format": "uri"
          },
          "voiceId": {
            "type": "string",
            "minLength": 8
          },
          "musicTrackId": {
            "type": "string",
            "maxLength": 40
          },
          "improveConsistency": {
            "type": "boolean",
            "default": true
          },
          "continuousMode": {
            "type": "boolean",
            "default": false
          },
          "premiumVideoModelId": {
            "type": "string",
            "maxLength": 40
          },
          "mediaItems": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/MediaItem"
            }
          }
        }
      },
      "RenderResponse": {
        "type": "object",
        "required": [
          "projectId",
          "renderJobId",
          "debited",
          "balanceBefore"
        ],
        "properties": {
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "renderJobId": {
            "type": "string",
            "format": "uuid"
          },
          "debited": {
            "type": "integer",
            "description": "Credits charged when the render starts."
          },
          "balanceBefore": {
            "type": "integer",
            "description": "Credit balance immediately before this charge."
          }
        }
      },
      "AcceptedJob": {
        "type": "object",
        "required": [
          "jobId",
          "kind",
          "model",
          "creditsCharged",
          "status",
          "statusUrl"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "enum": [
              "video",
              "image"
            ]
          },
          "model": {
            "type": "string"
          },
          "creditsCharged": {
            "type": "integer"
          },
          "status": {
            "const": "queued"
          },
          "statusUrl": {
            "type": "string"
          }
        }
      },
      "AcceptedJobResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/AcceptedJob"
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "jobId",
          "kind",
          "model",
          "status",
          "creditsCharged"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "enum": [
              "video",
              "image"
            ]
          },
          "model": {
            "type": "string"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "ready",
              "failed"
            ]
          },
          "outputUrl": {
            "type": "string",
            "format": "uri"
          },
          "outputUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "outputPending": {
            "type": "boolean",
            "description": "True when a video is ready but a signed delivery URL is not yet available."
          },
          "durationSec": {
            "type": "integer"
          },
          "creditsCharged": {
            "type": "integer",
            "description": "Net charge after refunds."
          },
          "error": {
            "type": "string"
          }
        }
      },
      "JobResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Job"
          }
        }
      },
      "WebhookReady": {
        "type": "object",
        "required": [
          "ok",
          "status",
          "jobId",
          "kind"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "status": {
            "const": "ready"
          },
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "enum": [
              "video",
              "image"
            ]
          },
          "outputUrl": {
            "type": "string",
            "format": "uri"
          },
          "outputUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "durationSec": {
            "type": "integer"
          },
          "partial": {
            "type": "boolean"
          },
          "deliveredCount": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "WebhookFailed": {
        "type": "object",
        "required": [
          "ok",
          "status",
          "jobId",
          "error"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "status": {
            "const": "failed"
          },
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "error": {
            "type": "string"
          }
        }
      },
      "WebhookPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/WebhookReady"
          },
          {
            "$ref": "#/components/schemas/WebhookFailed"
          }
        ]
      },
      "FieldError": {
        "type": "object",
        "required": [
          "field",
          "message",
          "code"
        ],
        "properties": {
          "field": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        }
      },
      "ErrorBody": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            }
          },
          "required": {
            "type": "integer"
          },
          "balance": {
            "type": "integer"
          },
          "idempotencyKey": {
            "type": "string"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error",
          "docs"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          },
          "docs": {
            "const": "/docs/api"
          }
        }
      },
      "MediaItem": {
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "type": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "urlLowRes": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "imagePreview": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "noReencode": {
            "type": "boolean"
          }
        }
      },
      "CalculateCreditsRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tier": {
            "type": "string",
            "enum": [
              "base",
              "pro",
              "ultra"
            ],
            "default": "pro"
          },
          "mediaType": {
            "type": "string",
            "enum": [
              "ai-video",
              "ai-moving",
              "motion-graphics",
              "stock-videos",
              "user-upload",
              "static-images",
              "ai-action"
            ],
            "default": "ai-video"
          },
          "durationSec": {
            "type": "integer",
            "minimum": 15,
            "maximum": 300,
            "default": 30
          },
          "presetId": {
            "type": "string",
            "default": "stock"
          },
          "nbGenerations": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          }
        }
      },
      "CharacterRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "description": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "source": {
            "type": "string",
            "enum": [
              "avatar",
              "lora"
            ],
            "default": "avatar"
          },
          "voiceId": {
            "type": "string",
            "maxLength": 60
          }
        }
      },
      "MediaSearchRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "type": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "any"
            ],
            "default": "any"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          }
        }
      },
      "SuccessEnvelope": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "data": {
            "type": "object"
          },
          "docs": {
            "type": "string"
          }
        }
      },
      "AccountResponse": {
        "type": "object",
        "required": [
          "ok",
          "data",
          "docs"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "profileId",
              "plan",
              "credits"
            ],
            "properties": {
              "profileId": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "displayName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "avatarUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "about": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "timezone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "topics": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "plan": {
                "type": "string"
              },
              "credits": {
                "type": "number"
              },
              "aiSettings": {
                "type": [
                  "object",
                  "null"
                ]
              }
            }
          },
          "docs": {
            "const": "/docs/api"
          }
        }
      },
      "LegacyErrorEnvelope": {
        "type": "object",
        "description": "Legacy error envelope. POST /api/v1/render's own auth check and every response from /api/v1/characters, /api/v1/characters/{id}, and /api/v1/media-search use this shape instead of ErrorResponse. See /docs/api/errors for which endpoints return which envelope.",
        "required": [
          "ok",
          "error",
          "docs"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "detail": {
            "description": "A human-readable string, or a zod .flatten() validation object."
          },
          "docs": {
            "type": "string"
          }
        }
      },
      "RenderPipelineError": {
        "type": "object",
        "description": "POST /api/v1/render forwards most non-auth failures (400, 402, 429, and most 500s) verbatim from the internal render pipeline. The body always has a short string 'error' code; other fields vary by code (for example 'required' and 'balance' on insufficient_credits, or 'retryAfterSec' on rate_limited). Unlike ErrorResponse and LegacyErrorEnvelope, it has no 'ok' or 'docs' field.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": true
      }
    },
    "responses": {
      "BadJson": {
        "description": "Body is empty or is not JSON",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Key is missing, revoked, or invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Account needs more credits",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Model is not available",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "JobNotFound": {
        "description": "Job is not owned or does not exist",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "PriceChanged": {
        "description": "The live charge differs from maxCredits; error.required is current",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "Either the live charge changed (price_changed) or the Idempotency-Key was already used with a different request body (idempotency_key_reuse)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Settings are invalid or a reference field is unsupported",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "GenerationFailed": {
        "description": "Job could not be started",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "StatusFailed": {
        "description": "Job status could not be checked",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Models or rate limiting are temporarily unavailable",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Timeout": {
        "description": "Start timed out. The error echoes idempotencyKey when supplied; retry with the same value.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    }
  }
}
