openapi: 3.1.0
info:
  title: CheapVids API
  version: 1.0.0
servers:
  - url: https://api.cheapvids.com
tags:
  - description: Create images and videos, poll them, cancel them and download results
    name: Generations
  - description: Models you can use and their prices
    name: Models
  - description: Your plans, credits, limits and usage
    name: Account
  - description: Image generation compatible with the OpenAI client libraries
    name: OpenAI compatible
paths:
  /v1/account:
    get:
      description: "Both plan groups side by side and independent: Studio daily quota and Credits balance, with the limits that apply to each, plus the result download window and limits."
      operationId: getAccount
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
          description: OK
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Get your account
      tags:
        - Account
  /v1/generations:
    post:
      description: "Starts an asynchronous job. `plan` is required and the system never switches group: when the chosen group is out of quota or credits you get an error and can retry with the other plan. Poll `GET /v1/generations/{id}` or receive a webhook."
      operationId: createGeneration
      parameters:
        - description: 1 to 255 printable ASCII characters. Repeating a request with the same key and body returns the first job and charges once. Without it, an identical request within 120 seconds also returns the first job; send a different key per call to create variants of the same prompt
          in: header
          name: Idempotency-Key
          schema:
            description: 1 to 255 printable ASCII characters. Repeating a request with the same key and body returns the first job and charges once. Without it, an identical request within 120 seconds also returns the first job; send a different key per call to create variants of the same prompt
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGenerationRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
          description: "Replay: an earlier job for the same Idempotency-Key and body, nothing new was created or charged"
          headers:
            Idempotent-Replayed:
              description: Always true on this response
              schema:
                type: string
            Location:
              description: URL of the job
              schema:
                type: string
        "201":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
          description: Created
          headers:
            Idempotent-Replayed:
              schema:
                description: true when an earlier job for the same request was returned
                type: string
            Location:
              schema:
                description: URL of the job
                type: string
            X-RateLimit-Limit:
              schema:
                description: Requests allowed per minute for the chosen plan
                format: int64
                type: integer
            X-RateLimit-Remaining:
              schema:
                description: Requests left in the current minute
                format: int64
                type: integer
            X-RateLimit-Reset:
              schema:
                description: Unix time when the current minute ends
                format: int64
                type: integer
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Create an image or video
      tags:
        - Generations
  /v1/generations/{id}:
    get:
      description: Returns the job. With `wait` the request is held open until the job reaches a final status or the wait ends, whichever comes first.
      operationId: getGeneration
      parameters:
        - description: Generation id
          in: path
          name: id
          required: true
          schema:
            description: Generation id
            type: string
        - description: Seconds to hold the request open until the job finishes. Values above 60 are treated as 60
          explode: false
          in: query
          name: wait
          schema:
            description: Seconds to hold the request open until the job finishes. Values above 60 are treated as 60
            format: int64
            minimum: 0
            type: integer
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
          description: OK
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Get a job
      tags:
        - Generations
  /v1/generations/{id}/cancel:
    post:
      description: Cancels a job that has not started running and refunds it in full. A job that is already processing returns `not_cancellable`.
      operationId: cancelGeneration
      parameters:
        - description: Generation id
          in: path
          name: id
          required: true
          schema:
            description: Generation id
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
          description: OK
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Cancel a job
      tags:
        - Generations
  /v1/generations/{id}/content:
    get:
      description: "CheapVids streams the result of a job created through the API; nothing is stored. Authenticate with your API key, or use the signed `output.url` (`exp` and `sig`) which works without a key, for example in a `<video>` tag. Supports one `Range` per request. After the download window (`results.api_ttl_hours` in `GET /v1/account`) the result is gone: `410 result_expired`. Per-account limits on parallel streams, daily bytes and downloads per result apply and answer `429` with `Retry-After`."
      operationId: downloadContent
      parameters:
        - description: Generation id
          in: path
          name: id
          required: true
          schema:
            description: Generation id
            type: string
        - description: Expiry (Unix seconds) of a signed URL. Use together with sig instead of an API key
          explode: false
          in: query
          name: exp
          schema:
            description: Expiry (Unix seconds) of a signed URL. Use together with sig instead of an API key
            type: string
        - description: Signature of a signed URL, as returned in output.url
          explode: false
          in: query
          name: sig
          schema:
            description: Signature of a signed URL, as returned in output.url
            type: string
        - description: "Set to 1 to send Content-Disposition: attachment"
          explode: false
          in: query
          name: download
          schema:
            description: "Set to 1 to send Content-Disposition: attachment"
            type: string
        - description: One byte range, for example bytes=0-1048575. Used for seeking in video
          in: header
          name: Range
          schema:
            description: One byte range, for example bytes=0-1048575. Used for seeking in video
            type: string
      responses:
        "200":
          description: The whole result
        "206":
          description: The requested byte range
      security:
        - bearerAuth: []
        - {}
      summary: Download the result of a job
      tags:
        - Generations
    head:
      description: Same checks and limits as the download. Returns the headers, including Content-Length with the total size, and no body.
      operationId: headContent
      parameters:
        - description: Generation id
          in: path
          name: id
          required: true
          schema:
            description: Generation id
            type: string
        - description: Expiry (Unix seconds) of a signed URL. Use together with sig instead of an API key
          explode: false
          in: query
          name: exp
          schema:
            description: Expiry (Unix seconds) of a signed URL. Use together with sig instead of an API key
            type: string
        - description: Signature of a signed URL, as returned in output.url
          explode: false
          in: query
          name: sig
          schema:
            description: Signature of a signed URL, as returned in output.url
            type: string
        - description: "Set to 1 to send Content-Disposition: attachment"
          explode: false
          in: query
          name: download
          schema:
            description: "Set to 1 to send Content-Disposition: attachment"
            type: string
        - description: One byte range, for example bytes=0-1048575. Used for seeking in video
          in: header
          name: Range
          schema:
            description: One byte range, for example bytes=0-1048575. Used for seeking in video
            type: string
      responses:
        "200":
          description: Headers of the result
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
        - {}
      summary: Get the size and type of the result of a job
      tags:
        - Generations
  /v1/images/generations:
    post:
      description: Works with the OpenAI client libraries when `base_url` is set to `https://api.cheapvids.com/v1`. Only `images/generations` is supported, with `n` = 1; there is no `edits` or `variations`. The request waits for the image and answers `504 generation_pending` with `details.generation_id` when it takes too long; poll `GET /v1/generations/{id}` then. Retrying the same request does not charge twice.
      operationId: createImage
      parameters:
        - description: Optional. Same key and body returns the first job
          in: header
          name: Idempotency-Key
          schema:
            description: Optional. Same key and body returns the first job
            type: string
        - description: studio or credits. Without plan or X-Plan a model that is not in Studio uses credits; a Studio model needs one of them
          in: header
          name: X-Plan
          schema:
            description: studio or credits. Without plan or X-Plan a model that is not in Studio uses credits; a Studio model needs one of them
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateImageRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImageResponse"
          description: OK
          headers:
            X-RateLimit-Limit:
              schema:
                format: int64
                type: integer
            X-RateLimit-Remaining:
              schema:
                format: int64
                type: integer
            X-RateLimit-Reset:
              schema:
                format: int64
                type: integer
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Create an image (OpenAI compatible)
      tags:
        - OpenAI compatible
  /v1/models:
    get:
      description: The models on sale with their capabilities, parameters, credit price table and whether they are included in the Studio plan (`studio_eligible`). Same data as the web app.
      operationId: listModels
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"
          description: OK
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: List models
      tags:
        - Models
  /v1/usage:
    get:
      description: Jobs per UTC day, plan group, model and kind. At most 31 days; the default is the last 7 days.
      operationId: getUsage
      parameters:
        - description: "First day (UTC), YYYY-MM-DD. Default: 6 days before to"
          explode: false
          in: query
          name: from
          schema:
            description: "First day (UTC), YYYY-MM-DD. Default: 6 days before to"
            examples:
              - 2026-10-01
            pattern: ^\d{4}-\d{2}-\d{2}$
            type: string
        - description: "Last day (UTC, inclusive), YYYY-MM-DD. Default: today"
          explode: false
          in: query
          name: to
          schema:
            description: "Last day (UTC, inclusive), YYYY-MM-DD. Default: today"
            examples:
              - 2026-10-07
            pattern: ^\d{4}-\d{2}-\d{2}$
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageReport"
          description: OK
        default:
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
          description: Error
      security:
        - bearerAuth: []
      summary: Get usage by day
      tags:
        - Account
components:
  schemas:
    Account:
      additionalProperties: false
      properties:
        api:
          $ref: "#/components/schemas/AccountAPI"
        balance_usd_cents:
          format: int64
          type: integer
        plans:
          $ref: "#/components/schemas/AccountPlans"
        results:
          $ref: "#/components/schemas/ResultsInfo"
        user:
          $ref: "#/components/schemas/AccountUser"
      required:
        - user
        - balance_usd_cents
        - plans
        - api
        - results
      type: object
    AccountAPI:
      additionalProperties: false
      properties:
        key_rpm_cap:
          description: Requests per minute allowed per API key across all endpoints
          format: int64
          type: integer
      required:
        - key_rpm_cap
      type: object
    AccountPlans:
      additionalProperties: false
      properties:
        credits:
          $ref: "#/components/schemas/CreditsPlan"
        studio:
          $ref: "#/components/schemas/StudioPlan"
      required:
        - studio
        - credits
      type: object
    AccountUser:
      additionalProperties: false
      properties:
        email:
          type: string
        id:
          type: string
      required:
        - id
        - email
      type: object
    CreateGenerationRequest:
      additionalProperties: false
      properties:
        model:
          description: Model id from GET /v1/models
          examples:
            - nano-banana-2
          type: string
        params:
          $ref: "#/components/schemas/Params"
          description: Prompt and options. Input images go in params.images as base64
        plan:
          description: Plan group to charge. Required. Studio draws on the daily quota of your Studio plan; credits draws on your credit lots. The system never switches group for you
          enum:
            - studio
            - credits
          examples:
            - credits
          type: string
      required:
        - plan
        - model
        - params
      type: object
    CreateImageRequest:
      additionalProperties: true
      properties:
        model:
          description: Image model id from GET /v1/models
          examples:
            - nano-banana-2
          type: string
        n:
          description: Must be 1 or omitted. Send separate requests for more images
          format: int64
          type: integer
        plan:
          description: "CheapVids extension: studio or credits. Also accepted as the X-Plan header"
          type: string
        prompt:
          description: What to create
          examples:
            - A lighthouse at dusk
          type: string
        quality:
          description: hd selects 2k resolution when the model supports it; other values are ignored
          type: string
        response_format:
          description: url (default) or b64_json
          type: string
        size:
          description: 1024x1024, 1536x1024, 1792x1024, 1024x1536, 1024x1792 or auto. Mapped to the nearest aspect ratio the model supports
          type: string
        user:
          description: Ignored
          type: string
      required:
        - model
        - prompt
      type: object
    CreditLot:
      additionalProperties: false
      properties:
        expires_at:
          format: date-time
          type: string
        images_only:
          type: boolean
        kind:
          enum:
            - plan
            - addon
            - trial
            - refund
          type: string
        remaining:
          format: int64
          type: integer
        watermark:
          description: Trial lots carry a watermark and are never used by API jobs
          type: boolean
      required:
        - kind
        - remaining
        - expires_at
        - images_only
        - watermark
      type: object
    CreditsPlan:
      additionalProperties: false
      properties:
        active:
          type: boolean
        auto_renew:
          type: boolean
        concurrency:
          format: int64
          type: integer
        create_rpm:
          description: Job creations per minute with plan=credits
          format: int64
          type: integer
        credits_remaining:
          description: Credits that jobs with plan=credits can spend, whether or not a Credits plan is active
          format: int64
          type: integer
        lots:
          items:
            $ref: "#/components/schemas/CreditLot"
          type:
            - array
            - "null"
        period_end:
          format: date-time
          type:
            - string
            - "null"
        tier:
          type:
            - string
            - "null"
      required:
        - active
        - tier
        - period_end
        - auto_renew
        - credits_remaining
        - lots
        - concurrency
        - create_rpm
      type: object
    DownloadLimits:
      additionalProperties: false
      properties:
        job_download_multiple:
          description: Each result can be downloaded this many times its size per UTC day
          format: int64
          type: integer
        used_bytes_today:
          description: Bytes downloaded today; null when the counter is unavailable
          format: int64
          type:
            - integer
            - "null"
        user_gib_per_day:
          description: GiB your account can download through the content endpoint per UTC day
          format: int64
          type: integer
      required:
        - user_gib_per_day
        - used_bytes_today
        - job_download_multiple
      type: object
    ErrorBody:
      additionalProperties: false
      properties:
        code:
          description: Machine-readable error code
          type: string
        details:
          additionalProperties: {}
          description: Code-specific details
          type: object
        message:
          description: Human-readable message
          type: string
        plan:
          description: Plan group (studio or credits) the error applies to, or null
          type:
            - string
            - "null"
      required:
        - code
        - message
        - plan
        - details
      type: object
    ErrorEnvelope:
      additionalProperties: false
      properties:
        error:
          $ref: "#/components/schemas/ErrorBody"
      required:
        - error
      type: object
    Generation:
      additionalProperties: false
      properties:
        cost_credits:
          format: int64
          type:
            - integer
            - "null"
        created_at:
          format: date-time
          type: string
        error:
          oneOf:
            - $ref: "#/components/schemas/PublicError"
            - type: "null"
        error_code:
          type:
            - string
            - "null"
        finished_at:
          format: date-time
          type:
            - string
            - "null"
        id:
          type: string
        kind:
          type: string
        mode:
          type: string
        model:
          type: string
        next:
          $ref: "#/components/schemas/Next"
          description: Present while the job is not finished
        output:
          oneOf:
            - $ref: "#/components/schemas/PublicOutput"
            - type: "null"
        params:
          $ref: "#/components/schemas/PublicParams"
        plan:
          type: string
        poster_url:
          type:
            - string
            - "null"
        preview_url:
          type:
            - string
            - "null"
        prompt:
          type:
            - string
            - "null"
        queued_at:
          format: date-time
          type: string
        quota_kind:
          type:
            - string
            - "null"
        refunded:
          type: boolean
        result_expires_at:
          format: date-time
          type:
            - string
            - "null"
        started_at:
          format: date-time
          type:
            - string
            - "null"
        status:
          type: string
        thumb_url:
          type:
            - string
            - "null"
        watermark:
          type: boolean
      required:
        - id
        - model
        - kind
        - mode
        - plan
        - status
        - prompt
        - params
        - cost_credits
        - quota_kind
        - watermark
        - refunded
        - error_code
        - error
        - output
        - preview_url
        - thumb_url
        - poster_url
        - result_expires_at
        - queued_at
        - started_at
        - finished_at
        - created_at
      type: object
    ImageData:
      additionalProperties: false
      properties:
        b64_json:
          description: The image, base64 encoded (response_format b64_json)
          type: string
        url:
          description: CheapVids URL of the image, valid for the download window
          type: string
      type: object
    ImageResponse:
      additionalProperties: false
      properties:
        created:
          description: Unix time
          format: int64
          type: integer
        data:
          items:
            $ref: "#/components/schemas/ImageData"
          type:
            - array
            - "null"
      required:
        - created
        - data
      type: object
    InputImage:
      additionalProperties: false
      properties:
        data:
          type: string
        mime:
          type: string
        upload_id:
          type: string
      type: object
    ModelList:
      additionalProperties: false
      properties:
        data:
          description: Models you can use, with prices and parameters
          items:
            $ref: "#/components/schemas/PublicModel"
          type:
            - array
            - "null"
      required:
        - data
      type: object
    Next:
      additionalProperties: false
      properties:
        action:
          description: "What to do next: poll the job again"
          enum:
            - poll
          type: string
        after_seconds:
          description: Seconds to wait before polling again
          format: int64
          type: integer
      required:
        - action
        - after_seconds
      type: object
    Params:
      additionalProperties: false
      properties:
        aspect_ratio:
          type: string
        duration:
          format: int64
          type: integer
        images:
          items:
            $ref: "#/components/schemas/InputImage"
          type:
            - array
            - "null"
        mode:
          type: string
        prompt:
          type: string
        resolution:
          type: string
      required:
        - prompt
      type: object
    PublicError:
      additionalProperties: false
      properties:
        code:
          type: string
        message:
          type: string
        reason:
          type:
            - string
            - "null"
      required:
        - code
        - message
        - reason
      type: object
    PublicImage:
      additionalProperties: false
      properties:
        height:
          format: int64
          type: integer
        index:
          format: int64
          type: integer
        mime:
          type: string
        width:
          format: int64
          type: integer
      required:
        - index
        - mime
        - width
        - height
      type: object
    PublicModel:
      additionalProperties: false
      properties:
        capabilities:
          items:
            type: string
          type:
            - array
            - "null"
        id:
          type: string
        kind:
          type: string
        max_prompt_chars:
          format: int64
          type: integer
        max_reference_images:
          format: int64
          type: integer
        name:
          type: string
        params_schema:
          items:
            $ref: "#/components/schemas/PublicSchemaParam"
          type:
            - array
            - "null"
        price_table:
          items:
            $ref: "#/components/schemas/PublicPrice"
          type:
            - array
            - "null"
        status:
          type: string
        studio_eligible:
          type: boolean
      required:
        - id
        - name
        - kind
        - studio_eligible
        - status
        - capabilities
        - max_reference_images
        - max_prompt_chars
        - params_schema
        - price_table
      type: object
    PublicOutput:
      additionalProperties: false
      properties:
        bytes:
          format: int64
          type: integer
        expires_at:
          format: date-time
          type: string
        mime:
          type: string
        url:
          type: string
      required:
        - url
        - mime
        - bytes
        - expires_at
      type: object
    PublicParams:
      additionalProperties: false
      properties:
        aspect_ratio:
          type: string
        duration:
          format: int64
          type: integer
        images:
          items:
            $ref: "#/components/schemas/PublicImage"
          type:
            - array
            - "null"
        resolution:
          type: string
      type: object
    PublicPrice:
      additionalProperties: false
      properties:
        credits:
          format: int64
          type: integer
        params:
          additionalProperties: {}
          type: object
      required:
        - params
        - credits
      type: object
    PublicSchemaParam:
      additionalProperties: false
      properties:
        default: {}
        max:
          format: int64
          type: integer
        min:
          format: int64
          type: integer
        name:
          type: string
        type:
          type: string
        values:
          items: {}
          type:
            - array
            - "null"
      required:
        - name
        - type
      type: object
    QuotaCount:
      additionalProperties: false
      properties:
        limit:
          format: int64
          type: integer
        remaining:
          format: int64
          type: integer
        used:
          format: int64
          type: integer
      required:
        - limit
        - used
        - remaining
      type: object
    ResultsInfo:
      additionalProperties: false
      properties:
        api_ttl_hours:
          description: Hours after completion during which a result can be downloaded; download before it ends
          format: int64
          type: integer
        download:
          $ref: "#/components/schemas/DownloadLimits"
      required:
        - api_ttl_hours
        - download
      type: object
    StudioPlan:
      additionalProperties: false
      properties:
        active:
          type: boolean
        auto_renew:
          type: boolean
        concurrency:
          description: Jobs that run at once; more are queued
          format: int64
          type: integer
        create_rpm:
          description: Job creations per minute with plan=studio
          format: int64
          type: integer
        period_end:
          format: date-time
          type:
            - string
            - "null"
        quota:
          description: Daily quota; null when no Studio plan is active
          oneOf:
            - $ref: "#/components/schemas/StudioQuota"
            - type: "null"
        tier:
          type:
            - string
            - "null"
      required:
        - active
        - tier
        - period_end
        - auto_renew
        - quota
        - concurrency
        - create_rpm
      type: object
    StudioQuota:
      additionalProperties: false
      properties:
        day:
          description: UTC day the counters belong to
          type: string
        image:
          $ref: "#/components/schemas/QuotaCount"
        resets_at:
          description: Next 00:00 UTC, when the quota renews
          format: date-time
          type: string
        video:
          $ref: "#/components/schemas/QuotaCount"
      required:
        - day
        - resets_at
        - video
        - image
      type: object
    UsageReport:
      additionalProperties: false
      properties:
        data:
          items:
            $ref: "#/components/schemas/UsageRow"
          type:
            - array
            - "null"
        from:
          type: string
        to:
          type: string
        totals:
          $ref: "#/components/schemas/UsageTotals"
      required:
        - from
        - to
        - data
        - totals
      type: object
    UsageRow:
      additionalProperties: false
      properties:
        cancelled:
          format: int64
          type: integer
        completed:
          format: int64
          type: integer
        credits:
          description: Credits charged by completed jobs (0 for the studio plan)
          format: int64
          type: integer
        day:
          description: UTC day, YYYY-MM-DD
          type: string
        failed:
          format: int64
          type: integer
        kind:
          enum:
            - image
            - video
          type: string
        model:
          type: string
        plan:
          enum:
            - studio
            - credits
          type: string
      required:
        - day
        - plan
        - model
        - kind
        - completed
        - failed
        - cancelled
        - credits
      type: object
    UsageTotals:
      additionalProperties: false
      properties:
        completed:
          format: int64
          type: integer
        credits:
          format: int64
          type: integer
      required:
        - completed
        - credits
      type: object
  securitySchemes:
    bearerAuth:
      description: "API key from your account, sent as Authorization: Bearer cv_live_..."
      scheme: bearer
      type: http
