openapi: 3.1.2
info:
  title: ColoringBookify API
  version: 1.1.0
  summary: Plan, create, manage, and export complete coloring books.
  description: >-
    The v1 REST API is available to active Business accounts. Generation is
    asynchronous, consumes account credits, and requires idempotency keys.
    Resource identifiers and pagination cursors are opaque. Every authenticated
    response includes X-ColoringBookify-Credits-Available and
    X-ColoringBookify-Credits-Consumed. Each operation also declares its account
    credit cost through x-credit-cost.
servers:
  - url: https://coloringbookify.com
security:
  - bearerApiKey: []
tags:
  - name: Account
  - name: Planning
  - name: Print
  - name: Operations
  - name: Characters
  - name: Books
  - name: Pages
paths:
  /api/v1/me:
    get:
      tags: [Account]
      operationId: getAccount
      summary: Get the authenticated account, credits, and capabilities.
      x-credit-cost: 0
      responses:
        "200": { description: Account details, content: { application/json: { schema: { $ref: "#/components/schemas/AccountEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
  /api/v1/print_formats:
    get:
      tags: [Print]
      operationId: listPrintFormats
      summary: List supported PDF trim sizes and recommended image dimensions.
      description: Use a format whose aspect matches the book. Defaults and recommendations are included in meta.
      x-credit-cost: 0
      responses:
        "200": { description: Print formats and aspect recommendations, content: { application/json: { schema: { $ref: "#/components/schemas/PrintFormatListEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
  /api/v1/book_plans:
    post:
      tags: [Planning, Books]
      operationId: createBookPlan
      summary: Create a stateless, editable plan and exact generation payload.
      description: The plan is not persisted. The client reviews or edits generation_request, then submits it to POST /api/v1/books with a new idempotency key.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/BookPlanInput" } } }
      responses:
        "200": { description: Editable plan, exact credit estimate, and generation request, content: { application/json: { schema: { $ref: "#/components/schemas/BookPlanEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
        "429": { $ref: "#/components/responses/RateLimited" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /api/v1/operations:
    get:
      tags: [Operations]
      operationId: listOperations
      summary: List asynchronous operations for the account.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
      responses:
        "200": { description: A page of operations, content: { application/json: { schema: { $ref: "#/components/schemas/OperationListEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
  /api/v1/operations/{id}:
    get:
      tags: [Operations]
      operationId: getOperation
      summary: Poll an asynchronous operation.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200": { description: Current state; nonterminal responses include Retry-After, content: { application/json: { schema: { $ref: "#/components/schemas/OperationEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/characters:
    get:
      tags: [Characters]
      operationId: listCharacters
      summary: List active or archived reusable characters.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
        - { name: archived, in: query, schema: { type: boolean, default: false } }
      responses:
        "200": { description: A page of characters, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterListEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
    post:
      tags: [Characters]
      operationId: createCharacter
      summary: Create and asynchronously generate a reusable character.
      x-credit-cost: 1
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json: { schema: { $ref: "#/components/schemas/CharacterGenerationInput" } }
          multipart/form-data: { schema: { $ref: "#/components/schemas/CharacterGenerationInput" } }
      responses:
        "202": { $ref: "#/components/responses/CharacterMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/characters/{id}:
    get:
      tags: [Characters]
      operationId: getCharacter
      summary: Get an owned character.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200": { description: Character details, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Characters]
      operationId: updateCharacter
      summary: Update a character name or public-preview setting.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/CharacterUpdateInput" } } }
      responses:
        "200": { description: Updated character, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    put:
      tags: [Characters]
      operationId: replaceCharacterMutableFields
      summary: Update a character name or public-preview setting.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/CharacterUpdateInput" } } }
      responses:
        "200": { description: Updated character, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    delete:
      tags: [Characters]
      operationId: archiveCharacter
      summary: Archive a character.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200": { description: Archived character, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/characters/{id}/reference_image:
    get:
      tags: [Characters]
      operationId: getCharacterReferenceImage
      summary: Download an owned character's generated reference image.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200":
          description: Generated image bytes.
          content:
            image/png: { schema: { type: string, format: binary } }
            image/jpeg: { schema: { type: string, format: binary } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/characters/{id}/regenerate:
    post:
      tags: [Characters]
      operationId: regenerateCharacter
      summary: Asynchronously regenerate an owned character.
      x-credit-cost: 1
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          application/json: { schema: { $ref: "#/components/schemas/CharacterGenerationInput" } }
          multipart/form-data: { schema: { $ref: "#/components/schemas/CharacterGenerationInput" } }
      responses:
        "202": { $ref: "#/components/responses/CharacterMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/characters/{id}/restore:
    post:
      tags: [Characters]
      operationId: restoreCharacter
      summary: Restore an archived character.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200": { description: Restored character, content: { application/json: { schema: { $ref: "#/components/schemas/CharacterEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/books:
    get:
      tags: [Books]
      operationId: listBooks
      summary: List books owned by the account.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
      responses:
        "200": { description: A page of books, content: { application/json: { schema: { $ref: "#/components/schemas/BookListEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
    post:
      tags: [Books]
      operationId: createBook
      summary: Create a book and asynchronously generate its pages and cover.
      description: Each requested coloring or special page consumes one credit; automatically generated covers and structural pages do not.
      x-credit-cost: One credit per requested coloring, test-color, dedication, or ending page.
      parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
      requestBody:
        required: true
        content:
          application/json: { schema: { $ref: "#/components/schemas/BookCreateInput" } }
          multipart/form-data: { schema: { $ref: "#/components/schemas/BookCreateInput" } }
      responses:
        "202": { $ref: "#/components/responses/BookMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/books/{id}:
    get:
      tags: [Books]
      operationId: getBook
      summary: Get an owned book with ordered page summaries.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200": { description: Book details, content: { application/json: { schema: { $ref: "#/components/schemas/BookEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Books]
      operationId: updateBook
      summary: Update book metadata or reusable characters.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/BookUpdateInput" } } }
      responses:
        "200": { description: Updated book, content: { application/json: { schema: { $ref: "#/components/schemas/BookEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    put:
      tags: [Books]
      operationId: replaceBookMutableFields
      summary: Update book metadata or reusable characters.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/BookUpdateInput" } } }
      responses:
        "200": { description: Updated book, content: { application/json: { schema: { $ref: "#/components/schemas/BookEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    delete:
      tags: [Books]
      operationId: deleteBook
      summary: Delete an owned book.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "204": { description: Book deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/books/{id}/pdf:
    get:
      tags: [Books, Print]
      operationId: downloadBookPdf
      summary: Generate and download the final print-ready book PDF.
      description: This standard PDF export does not expose Lulu or Amazon KDP publishing workflows. The book must have a ready cover and every included page must be ready with an image.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - name: page_size
          in: query
          required: false
          description: A format ID from GET /api/v1/print_formats whose aspect matches the book. Defaults to the recommended format for the book aspect.
          schema: { type: string }
      responses:
        "200":
          description: PDF bytes.
          content: { application/pdf: { schema: { type: string, format: binary } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/books/{book_id}/pages:
    post:
      tags: [Books, Pages]
      operationId: addBookPage
      summary: Generate a new book page or attach an existing ready page.
      description: New generation requires Idempotency-Key and one credit. Attaching existing_page_id is synchronous and free.
      x-credit-cost: 0 when attaching an existing page; 1 when generating a page.
      parameters:
        - $ref: "#/components/parameters/BookId"
        - $ref: "#/components/parameters/OptionalIdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/ExistingPageInput"
                - $ref: "#/components/schemas/PageCreateInput"
          multipart/form-data:
            schema: { $ref: "#/components/schemas/PageCreateInput" }
      responses:
        "201": { description: Existing page attached, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "202": { $ref: "#/components/responses/PageMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/books/{book_id}/pages/{id}:
    delete:
      tags: [Books, Pages]
      operationId: detachBookPage
      summary: Detach a page from a book without deleting it.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/BookId"
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "204": { description: Page detached }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/books/{id}/pages/order:
    patch:
      tags: [Books, Pages]
      operationId: reorderBookPages
      summary: Replace the page order for a book.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/PageOrderInput" } } }
      responses:
        "200": { description: Ordered pages, content: { application/json: { schema: { $ref: "#/components/schemas/PageListDataEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/pages:
    get:
      tags: [Pages]
      operationId: listPages
      summary: List pages owned by the account.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
      responses:
        "200": { description: A page of pages, content: { application/json: { schema: { $ref: "#/components/schemas/PageListEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
    post:
      tags: [Pages]
      operationId: createPage
      summary: Create and asynchronously generate a standalone page.
      description: Generation consumes one credit.
      x-credit-cost: 1
      parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
      requestBody:
        required: true
        content:
          application/json: { schema: { $ref: "#/components/schemas/PageCreateInput" } }
          multipart/form-data: { schema: { $ref: "#/components/schemas/PageCreateInput" } }
      responses:
        "202": { $ref: "#/components/responses/PageMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/pages/import:
    post:
      tags: [Pages]
      operationId: importFinishedPage
      summary: Import a finished page created outside ColoringBookify.
      description: Creates a ready standalone page without AI generation. JPEG, PNG, and WebP uploads are normalized to PNG and padded with white, without cropping, when a small aspect adjustment is needed.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
      requestBody:
        required: true
        content: { multipart/form-data: { schema: { $ref: "#/components/schemas/FinishedPageImportInput" } } }
      responses:
        "201": { description: Imported ready page, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/pages/{id}:
    get:
      tags: [Pages]
      operationId: getPage
      summary: Get an owned page.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "200": { description: Page details, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Pages]
      operationId: updatePage
      summary: Update page metadata or reusable characters.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/PageUpdateInput" } } }
      responses:
        "200": { description: Updated page, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    put:
      tags: [Pages]
      operationId: replacePageMutableFields
      summary: Update page metadata or reusable characters.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: "#/components/schemas/PageUpdateInput" } } }
      responses:
        "200": { description: Updated page, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
    delete:
      tags: [Pages]
      operationId: deletePage
      summary: Delete an owned page.
      x-credit-cost: 0
      parameters: [{ $ref: "#/components/parameters/ResourceId" }]
      responses:
        "204": { description: Page deleted }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/pages/{id}/image:
    get:
      tags: [Pages]
      operationId: getPageImage
      summary: Download an owned page's generated image.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - { name: download, in: query, description: Return Content-Disposition attachment when true., schema: { type: boolean, default: false } }
      responses:
        "200":
          description: Generated image bytes.
          content:
            image/png: { schema: { type: string, format: binary } }
            image/jpeg: { schema: { type: string, format: binary } }
            image/svg+xml: { schema: { type: string, format: binary } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
    put:
      tags: [Pages]
      operationId: replacePageImage
      summary: Replace a page image with a finished image created outside ColoringBookify.
      description: Marks the page ready, clears its prior safe error, and does not call an AI provider. The same normalization rules as page import apply.
      x-credit-cost: 0
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content: { multipart/form-data: { schema: { $ref: "#/components/schemas/FinishedPageReplacementInput" } } }
      responses:
        "200": { description: Updated ready page, content: { application/json: { schema: { $ref: "#/components/schemas/PageEnvelope" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
  /api/v1/pages/{id}/regenerate:
    post:
      tags: [Pages]
      operationId: regeneratePage
      summary: Asynchronously regenerate an owned page.
      description: Regeneration consumes one credit.
      x-credit-cost: 1
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202": { $ref: "#/components/responses/PageMutationAccepted" }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/InsufficientCredits" }
        "403": { $ref: "#/components/responses/BusinessPlanRequired" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/ValidationFailed" }
components:
  securitySchemes:
    bearerApiKey: { type: http, scheme: bearer, bearerFormat: ColoringBookify API key }
  headers:
    CreditsAvailable:
      description: Account credits available after this HTTP request.
      schema: { type: integer, minimum: 0 }
    CreditsConsumed:
      description: Credits consumed by this HTTP request. An idempotent replay returns 0 even though its operation body reports the original charge.
      schema: { type: integer, minimum: 0 }
  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      description: Opaque resource identifier.
      schema: { type: string }
    BookId:
      name: book_id
      in: path
      required: true
      description: Opaque book identifier.
      schema: { type: string }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
    After:
      name: after
      in: query
      description: Opaque next_cursor from a previous response.
      schema: { type: string }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Unique key for safely retrying this request.
      schema: { type: string, minLength: 8, maxLength: 255 }
    OptionalIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Required for new page generation; unused when attaching an existing page.
      schema: { type: string, minLength: 8, maxLength: 255 }
  responses:
    Unauthorized: { description: Bearer API key missing or invalid, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    BusinessPlanRequired: { description: Active Business plan required, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    InvalidRequest: { description: Invalid request or idempotency key, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    InsufficientCredits: { description: Not enough credits, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    NotFound: { description: Resource missing or inaccessible, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    Conflict: { description: Idempotency, readiness, or generation conflict, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    ValidationFailed: { description: Validation failed, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    RateLimited: { description: Too many requests, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    ServiceUnavailable: { description: A dependent planning service is temporarily unavailable, headers: { X-ColoringBookify-Credits-Available: { $ref: "#/components/headers/CreditsAvailable" }, X-ColoringBookify-Credits-Consumed: { $ref: "#/components/headers/CreditsConsumed" } }, content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
    CharacterMutationAccepted:
      description: Character generation accepted.
      content: { application/json: { schema: { allOf: [{ $ref: "#/components/schemas/MutationEnvelope" }, { type: object, properties: { resource: { $ref: "#/components/schemas/Character" } } }] } } }
    BookMutationAccepted:
      description: Book generation accepted.
      content: { application/json: { schema: { allOf: [{ $ref: "#/components/schemas/MutationEnvelope" }, { type: object, properties: { resource: { $ref: "#/components/schemas/Book" } } }] } } }
    PageMutationAccepted:
      description: Page generation accepted.
      content: { application/json: { schema: { allOf: [{ $ref: "#/components/schemas/MutationEnvelope" }, { type: object, properties: { resource: { $ref: "#/components/schemas/Page" } } }] } } }
  schemas:
    AccountEnvelope:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [name, email, plan, credits, capabilities]
          properties:
            name: { type: string }
            email: { type: string, format: email }
            plan: { type: string, const: Business }
            credits: { type: integer }
            capabilities:
              type: object
              properties:
                characters: { type: boolean }
                api_read: { type: boolean, const: true }
                api_generation: { type: boolean, const: true }
    Character:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        handle: { type: string }
        description: { type: string }
        visual_description: { type: [string, "null"] }
        status: { type: string }
        archived: { type: boolean }
        public_preview: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        links: { $ref: "#/components/schemas/Links" }
    Book:
      type: object
      properties:
        id: { type: string }
        title: { type: [string, "null"] }
        description: { type: [string, "null"] }
        language: { type: string }
        aspect: { type: string, enum: [square, portrait, landscape] }
        level: { type: string }
        privacy: { type: string }
        outline_weight: { type: string }
        cover_style: { type: string }
        page_count: { type: integer }
        character_ids: { type: array, items: { type: string } }
        cover_page_id: { type: [string, "null"] }
        back_page_id: { type: [string, "null"] }
        pages: { type: array, items: { $ref: "#/components/schemas/Page" } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        links: { $ref: "#/components/schemas/Links" }
    Page:
      type: object
      properties:
        id: { type: string }
        title: { type: string }
        description: { type: [string, "null"] }
        status: { type: string }
        kind: { type: string }
        aspect: { type: string }
        level: { type: string }
        privacy: { type: string }
        outline_weight: { type: string }
        character_ids: { type: array, items: { type: string } }
        book_ids: { type: array, items: { type: string } }
        image_format: { type: string }
        book_index: { type: [integer, "null"] }
        error: { type: string, const: Generation failed. }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        links: { $ref: "#/components/schemas/Links" }
    Operation:
      type: object
      properties:
        id: { type: string }
        action: { type: string, enum: [character.create, character.regenerate, book.create, page.create, page.regenerate] }
        status: { type: string, enum: [queued, running, succeeded, partially_succeeded, failed] }
        credits:
          type: object
          properties: { charged: { type: integer }, refunded: { type: integer } }
        progress: { type: object, additionalProperties: true }
        resource:
          type: object
          properties: { type: { type: string }, id: { type: string }, link: { type: string } }
        error:
          type: object
          properties: { code: { type: string }, message: { type: string } }
        created_at: { type: string, format: date-time }
        started_at: { type: [string, "null"], format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        links: { $ref: "#/components/schemas/Links" }
    PrintFormat:
      type: object
      required: [id, aspect, trim_inches, trim_centimeters, recommended]
      properties:
        id: { type: string, example: us_letter }
        aspect: { type: string, enum: [square, portrait, landscape] }
        trim_inches: { $ref: "#/components/schemas/Dimensions" }
        trim_centimeters: { $ref: "#/components/schemas/Dimensions" }
        recommended: { type: boolean }
        recommendation: { type: string }
    Dimensions:
      type: object
      required: [width, height]
      properties: { width: { type: number }, height: { type: number } }
    Links: { type: object, additionalProperties: { type: string } }
    CharacterEnvelope: { type: object, properties: { data: { $ref: "#/components/schemas/Character" } } }
    BookEnvelope: { type: object, properties: { data: { $ref: "#/components/schemas/Book" } } }
    PageEnvelope: { type: object, properties: { data: { $ref: "#/components/schemas/Page" } } }
    OperationEnvelope: { type: object, properties: { data: { $ref: "#/components/schemas/Operation" } } }
    PrintFormatListEnvelope:
      type: object
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/PrintFormat" } }
        meta:
          type: object
          properties:
            image_aspects: { type: object, additionalProperties: true }
            defaults: { type: object, additionalProperties: { type: string } }
    PaginatedEnvelope:
      type: object
      properties:
        data: { type: array }
        meta:
          type: object
          properties: { limit: { type: integer }, next_cursor: { type: [string, "null"] }, has_more: { type: boolean } }
    CharacterListEnvelope: { allOf: [{ $ref: "#/components/schemas/PaginatedEnvelope" }, { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Character" } } } }] }
    BookListEnvelope: { allOf: [{ $ref: "#/components/schemas/PaginatedEnvelope" }, { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Book" } } } }] }
    PageListEnvelope: { allOf: [{ $ref: "#/components/schemas/PaginatedEnvelope" }, { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Page" } } } }] }
    OperationListEnvelope: { allOf: [{ $ref: "#/components/schemas/PaginatedEnvelope" }, { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Operation" } } } }] }
    PageListDataEnvelope: { type: object, properties: { data: { type: array, items: { $ref: "#/components/schemas/Page" } } } }
    MutationEnvelope:
      type: object
      required: [data, resource]
      properties: { data: { $ref: "#/components/schemas/Operation" }, resource: { type: object } }
    CharacterGenerationInput:
      type: object
      required: [character]
      properties:
        character:
          type: object
          properties:
            name: { type: string }
            description: { type: string }
            source_image: { type: string, format: binary }
    CharacterUpdateInput:
      type: object
      properties:
        character:
          type: object
          properties: { name: { type: string }, public_preview: { type: boolean } }
    BookCreateInput:
      type: object
      required: [book]
      properties:
        book:
          type: object
          required: [title, description, pages]
          properties:
            title: { type: string }
            description: { type: string }
            additional_instructions: { type: string }
            aspect: { type: string, enum: [square, portrait, landscape] }
            level: { type: string }
            privacy: { type: string }
            border_type: { type: string }
            outline_weight: { type: string }
            cover_style: { type: string }
            include_test_color_page: { type: boolean }
            language: { type: string }
            style_template_key: { type: string }
            style_prompt: { type: string }
            dedication_text: { type: string }
            ending_text: { type: string }
            reference_image: { type: string, format: binary }
            character_ids: { type: array, items: { type: string } }
            pages:
              type: array
              minItems: 1
              maxItems: 50
              items:
                type: object
                properties: { title: { type: string }, template_data: { type: object, additionalProperties: true } }
    BookPlanInput:
      type: object
      required: [book_plan]
      properties:
        book_plan:
          type: object
          required: [title, description]
          properties:
            title: { type: string, maxLength: 100, example: Ava and the Friendly Dragon }
            description: { type: string, maxLength: 2000, example: A 12-page portrait coloring book about Ava and her dragon exploring a magical forest. }
            additional_instructions: { type: string }
            style_template_key: { type: string, enum: [classic, bold_and_easy_first_words, custom], default: classic }
            style_prompt: { type: string, description: Custom visual directions when style_template_key is custom. }
            language: { type: string, enum: [en, es, fr, pt], default: en }
            aspect: { type: string, enum: [square, portrait, landscape] }
            level: { type: string, enum: [easy, medium, hard], default: easy }
            privacy: { type: string, enum: [is_private, is_public], default: is_private }
            border_type: { type: string, enum: [dotted, solid] }
            outline_weight: { type: string, enum: [fine, standard, bold, extra_bold] }
            cover_style: { type: string, enum: [classic, interactive_split, vibrant_hero], default: classic }
            include_test_color_page: { type: boolean }
            character_ids: { type: array, items: { type: string } }
    BookPlanEnvelope:
      type: object
      properties:
        data:
          type: object
          required: [type, persistent, plan, credits, generation_request]
          properties:
            type: { type: string, const: book_plan }
            persistent: { type: boolean, const: false }
            plan: { type: object, additionalProperties: true }
            credits:
              type: object
              properties:
                required: { type: integer }
                available: { type: integer }
                sufficient: { type: boolean }
            generation_request: { $ref: "#/components/schemas/BookCreateInput" }
            links: { $ref: "#/components/schemas/Links" }
    BookUpdateInput:
      type: object
      properties:
        book:
          type: object
          properties:
            title: { type: string }
            description: { type: string }
            privacy: { type: string }
            border_type: { type: string }
            cover_style: { type: string }
            character_ids: { type: array, items: { type: string } }
    PageCreateInput:
      type: object
      required: [page]
      properties:
        page:
          type: object
          required: [title]
          properties:
            title: { type: string }
            description: { type: string }
            kind: { type: string, enum: [coloring, freeform, dedication, quote, test_color, belongs] }
            aspect: { type: string, enum: [square, portrait, landscape] }
            level: { type: string }
            privacy: { type: string }
            border_type: { type: string }
            outline_weight: { type: string }
            input_image: { type: string, format: binary }
            character_ids: { type: array, items: { type: string } }
            template_data: { type: object, additionalProperties: true }
    ExistingPageInput:
      type: object
      required: [existing_page_id]
      properties: { existing_page_id: { type: string } }
    PageUpdateInput:
      type: object
      properties:
        page:
          type: object
          properties:
            title: { type: string }
            description: { type: string }
            privacy: { type: string }
            character_ids: { type: array, items: { type: string } }
    FinishedPageImportInput:
      type: object
      required: [page]
      properties:
        page:
          type: object
          required: [title, image]
          properties:
            title: { type: string }
            description: { type: string }
            aspect: { type: string, enum: [square, portrait, landscape], default: square }
            level: { type: string, enum: [easy, medium, hard], default: easy }
            privacy: { type: string, enum: [is_private, is_public], default: is_private }
            border_type: { type: string, enum: [dotted, solid] }
            outline_weight: { type: string, enum: [fine, standard, bold, extra_bold], default: standard }
            image: { type: string, format: binary, description: JPEG, PNG, or WebP; maximum 15 MB. }
    FinishedPageReplacementInput:
      type: object
      required: [page]
      properties:
        page:
          type: object
          required: [image]
          properties:
            image: { type: string, format: binary, description: JPEG, PNG, or WebP; maximum 15 MB. }
    PageOrderInput:
      type: object
      required: [page_ids]
      properties: { page_ids: { type: array, uniqueItems: true, items: { type: string } } }
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code: { type: string }
            message: { type: string }
            request_id: { type: string }
            details: { type: [array, object] }
