{
  "openapi": "3.1.0",
  "info": {
    "title": "Puzzle API",
    "version": "1",
    "description": "Run Nano Banana 2 and Nano Banana Pro via one API. Usage-based billing: the worst-case price is reserved when a job is accepted and charged on completion. Human-readable reference: https://puzzle-cloud.com/docs",
    "contact": {
      "url": "https://t.me/PUZZLE_Support_AI"
    }
  },
  "servers": [
    {
      "url": "https://api.puzzle-cloud.com",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "puz_live_…",
        "description": "API key created in the Puzzle console. Shown once, SHA-256 stored. Revoked keys answer 401."
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "The same puz_live_… key, for SDKs that only speak x-api-key."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "≤ 128 chars, unique within the workspace. Same key + same body → the stored job (202, Idempotent-Replayed: true); same key + different body → 409 idempotency_conflict",
        "schema": {
          "type": "string",
          "maxLength": 128
        },
        "example": "order-8812-cover"
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Request id — the same value as error.request_id in error bodies; quote it to support",
        "schema": {
          "type": "string",
          "example": "req_9a1b2c3d4e5f60718293a4b5"
        }
      },
      "RateLimit-Limit": {
        "description": "Requests allowed per minute for your workspace (IETF RateLimit header fields)",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current 60-second window",
        "schema": {
          "type": "integer",
          "example": 59
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until a slot frees up",
        "schema": {
          "type": "integer",
          "example": 42
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (429 only)",
        "schema": {
          "type": "integer",
          "example": 17
        }
      },
      "Idempotent-Replayed": {
        "description": "`true` when the response is the stored job of the same Idempotency-Key; absent otherwise",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "Cache-Control": {
        "description": "Caching policy of the response",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "JobStatus": {
        "type": "string",
        "enum": [
          "queued",
          "processing",
          "retry_scheduled",
          "completed",
          "failed",
          "cancelled"
        ],
        "description": "queued → processing | cancelled; processing → retry_scheduled | completed | failed; retry_scheduled → processing | failed"
      },
      "JobResult": {
        "type": "object",
        "description": "Download link of a completed job. The signature in the URL is the credential (no API key). expires_at is when the image is removed — currently 7 days after completion",
        "required": [
          "url",
          "mime_type",
          "width",
          "height",
          "expires_at"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "GET /v1/outputs/{key}?exp=…&sig=…",
            "example": "https://api.puzzle-cloud.com/v1/outputs/2026/09/job_3f9c2b7a1d4e8f60a1b2c3d4.png?exp=1790251209&sig=k2VhO3s9Q6bXw1LpR8dTzN4mY7cFgJ0eAqUiHvKo"
          },
          "mime_type": {
            "type": "string",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ]
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "example": {
          "url": "https://api.puzzle-cloud.com/v1/outputs/2026/09/job_3f9c2b7a1d4e8f60a1b2c3d4.png?exp=1790251209&sig=k2VhO3s9Q6bXw1LpR8dTzN4mY7cFgJ0eAqUiHvKo",
          "mime_type": "image/png",
          "width": 1024,
          "height": 1024,
          "expires_at": "2026-09-24T12:00:09.000Z"
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "request_id",
          "status",
          "model",
          "kind",
          "user",
          "settings",
          "price",
          "result",
          "error",
          "created_at",
          "started_at",
          "completed_at",
          "failed_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "job_3f9c2b7a1d4e8f60a1b2c3d4"
          },
          "request_id": {
            "type": "string",
            "example": "req_9a1b2c3d4e5f60718293a4b5",
            "description": "Same value as the X-Request-Id header of the response that created the job"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "model": {
            "type": "string",
            "enum": [
              "nano-banana-v2",
              "nano-banana-pro"
            ],
            "example": "nano-banana-pro"
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "url"
            ]
          },
          "user": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `user` value the job was created with (your end-user id)"
          },
          "settings": {
            "type": "object",
            "properties": {
              "aspect_ratio": {
                "type": "string",
                "enum": [
                  "1:1",
                  "16:9",
                  "9:16",
                  "4:3"
                ]
              },
              "resolution": {
                "type": "string",
                "enum": [
                  "1K",
                  "2K",
                  "4K"
                ],
                "description": "Effective resolution (default applied)"
              }
            }
          },
          "price": {
            "type": "object",
            "required": [
              "estimated_max",
              "charged",
              "currency"
            ],
            "properties": {
              "estimated_max": {
                "type": "string",
                "description": "USD, 6 decimals — reserved when accepted",
                "example": "0.191430"
              },
              "charged": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "USD actually charged once completed; null before that and on failed / cancelled jobs"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "USD"
                ]
              }
            }
          },
          "result": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/JobResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "Completed jobs fetched with GET /v1/jobs/{id} only; null in the list endpoint and in webhooks"
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "description": "Failed jobs only",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code",
                "enum": [
                  "provider_server",
                  "provider_network",
                  "provider_quota",
                  "provider_rate_limited",
                  "no_capacity",
                  "provider_safety_blocked",
                  "provider_invalid_request",
                  "provider_auth",
                  "provider_timeout_ambiguous",
                  "provider_unknown",
                  "source_url_unreachable",
                  "upload_not_found",
                  "unsupported_model",
                  "storage_or_internal_error",
                  "worker_lost",
                  "provider_ambiguous",
                  "executor_crashed"
                ],
                "example": "provider_server"
              },
              "message": {
                "type": "string",
                "description": "Customer-safe message; never a raw provider error"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "examples": [
          {
            "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
            "request_id": "req_9a1b2c3d4e5f60718293a4b5",
            "status": "queued",
            "model": "nano-banana-pro",
            "kind": "text",
            "user": "cust-42",
            "settings": {
              "aspect_ratio": "1:1",
              "resolution": "1K"
            },
            "price": {
              "estimated_max": "0.191430",
              "charged": null,
              "currency": "USD"
            },
            "result": null,
            "error": null,
            "created_at": "2026-09-17T12:00:00.000Z",
            "started_at": null,
            "completed_at": null,
            "failed_at": null
          },
          {
            "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
            "request_id": "req_9a1b2c3d4e5f60718293a4b5",
            "status": "completed",
            "model": "nano-banana-pro",
            "kind": "text",
            "user": "cust-42",
            "settings": {
              "aspect_ratio": "1:1",
              "resolution": "1K"
            },
            "price": {
              "estimated_max": "0.191430",
              "charged": "0.191430",
              "currency": "USD"
            },
            "result": {
              "url": "https://api.puzzle-cloud.com/v1/outputs/2026/09/job_3f9c2b7a1d4e8f60a1b2c3d4.png?exp=1790251209&sig=k2VhO3s9Q6bXw1LpR8dTzN4mY7cFgJ0eAqUiHvKo",
              "mime_type": "image/png",
              "width": 1024,
              "height": 1024,
              "expires_at": "2026-09-24T12:00:09.000Z"
            },
            "error": null,
            "created_at": "2026-09-17T12:00:00.000Z",
            "started_at": "2026-09-17T12:00:02.000Z",
            "completed_at": "2026-09-17T12:00:09.000Z",
            "failed_at": null
          },
          {
            "id": "job_7d21c0aa9e4b5f3c8d6e1f02",
            "request_id": "req_0b1c2d3e4f5061728394a5b6",
            "status": "failed",
            "model": "nano-banana-pro",
            "kind": "text",
            "user": "cust-42",
            "settings": {
              "aspect_ratio": "1:1",
              "resolution": "1K"
            },
            "price": {
              "estimated_max": "0.191430",
              "charged": null,
              "currency": "USD"
            },
            "result": null,
            "error": {
              "code": "provider_server",
              "message": "The generation backend failed. Retries exhausted; you were not charged."
            },
            "created_at": "2026-09-17T12:00:00.000Z",
            "started_at": "2026-09-17T12:00:02.000Z",
            "completed_at": null,
            "failed_at": "2026-09-17T12:07:41.000Z"
          }
        ]
      },
      "JobsPage": {
        "type": "object",
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque; pass back as `cursor`"
          }
        },
        "example": {
          "data": [
            {
              "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
              "request_id": "req_9a1b2c3d4e5f60718293a4b5",
              "status": "completed",
              "model": "nano-banana-pro",
              "kind": "text",
              "user": "cust-42",
              "settings": {
                "aspect_ratio": "1:1",
                "resolution": "1K"
              },
              "price": {
                "estimated_max": "0.191430",
                "charged": "0.191430",
                "currency": "USD"
              },
              "result": null,
              "error": null,
              "created_at": "2026-09-17T12:00:00.000Z",
              "started_at": "2026-09-17T12:00:02.000Z",
              "completed_at": "2026-09-17T12:00:09.000Z",
              "failed_at": null
            },
            {
              "id": "job_7d21c0aa9e4b5f3c8d6e1f02",
              "request_id": "req_0b1c2d3e4f5061728394a5b6",
              "status": "failed",
              "model": "nano-banana-pro",
              "kind": "text",
              "user": "cust-42",
              "settings": {
                "aspect_ratio": "1:1",
                "resolution": "1K"
              },
              "price": {
                "estimated_max": "0.191430",
                "charged": null,
                "currency": "USD"
              },
              "result": null,
              "error": {
                "code": "provider_server",
                "message": "The generation backend failed. Retries exhausted; you were not charged."
              },
              "created_at": "2026-09-17T11:58:12.000Z",
              "started_at": "2026-09-17T11:58:13.000Z",
              "completed_at": null,
              "failed_at": "2026-09-17T12:07:41.000Z"
            }
          ],
          "has_more": true,
          "next_cursor": "eyJjIjoiMjAyNi0wOS0xNyAxMTo1ODoxMi4wMDAwMDArMDAiLCJpIjoiam9iXzdkMjFjMGFhOWU0YjVmM2M4ZDZlMWYwMiJ9"
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "model",
          "kind",
          "resolution",
          "estimated_max",
          "currency",
          "pricing_version"
        ],
        "properties": {
          "model": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "url"
            ]
          },
          "resolution": {
            "type": "string",
            "enum": [
              "1K",
              "2K",
              "4K"
            ],
            "description": "Effective resolution (1K when omitted)"
          },
          "estimated_max": {
            "type": "string",
            "description": "Worst-case price of one such job, USD, 6 decimals — what a generation would reserve",
            "example": "0.191430"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "pricing_version": {
            "type": "string",
            "example": "pv_5e6f70819a2b3c4d5e6f7081"
          }
        },
        "example": {
          "model": "nano-banana-pro",
          "kind": "text",
          "resolution": "2K",
          "estimated_max": "0.191430",
          "currency": "USD",
          "pricing_version": "pv_5e6f70819a2b3c4d5e6f7081"
        }
      },
      "UsageBucket": {
        "type": "object",
        "properties": {
          "jobs": {
            "type": "integer"
          },
          "completed": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "cancelled": {
            "type": "integer"
          },
          "charged": {
            "type": "string",
            "description": "Sum of captured charges of completed jobs, USD, 6 decimals"
          },
          "images": {
            "type": "integer",
            "description": "Images delivered"
          }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              },
              "timezone": {
                "type": "string",
                "enum": [
                  "UTC"
                ]
              }
            }
          },
          "totals": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UsageBucket"
              },
              {
                "type": "object",
                "properties": {
                  "currency": {
                    "type": "string",
                    "enum": [
                      "USD"
                    ]
                  }
                }
              }
            ]
          },
          "by_day": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/UsageBucket"
                },
                {
                  "type": "object",
                  "properties": {
                    "day": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              ]
            }
          },
          "by_model": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/UsageBucket"
                },
                {
                  "type": "object",
                  "properties": {
                    "model": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "by_status": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/UsageBucket"
                },
                {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "by_user": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/UsageBucket"
                },
                {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              ]
            }
          },
          "by_api_key": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/UsageBucket"
                },
                {
                  "type": "object",
                  "properties": {
                    "api_key_id": {
                      "type": "string"
                    },
                    "api_key_prefix": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              ]
            }
          }
        },
        "examples": [
          {
            "period": {
              "start": "2026-09-01",
              "end": "2026-09-30",
              "timezone": "UTC"
            },
            "totals": {
              "jobs": 128,
              "completed": 121,
              "failed": 5,
              "cancelled": 2,
              "charged": "23.163030",
              "images": 121,
              "currency": "USD"
            },
            "by_day": [
              {
                "day": "2026-09-01",
                "jobs": 4,
                "completed": 4,
                "failed": 0,
                "cancelled": 0,
                "charged": "0.765720",
                "images": 4
              },
              {
                "day": "2026-09-02",
                "jobs": 7,
                "completed": 6,
                "failed": 1,
                "cancelled": 0,
                "charged": "1.148580",
                "images": 6
              }
            ],
            "by_model": [
              {
                "model": "nano-banana-pro",
                "jobs": 90,
                "completed": 86,
                "failed": 3,
                "cancelled": 1,
                "charged": "16.471980",
                "images": 86
              },
              {
                "model": "nano-banana-v2",
                "jobs": 38,
                "completed": 35,
                "failed": 2,
                "cancelled": 1,
                "charged": "6.691050",
                "images": 35
              }
            ],
            "by_status": [],
            "by_user": [],
            "by_api_key": []
          },
          {
            "period": {
              "start": "2026-09-01",
              "end": "2026-09-30",
              "timezone": "UTC"
            },
            "totals": {
              "jobs": 128,
              "completed": 121,
              "failed": 5,
              "cancelled": 2,
              "charged": "23.163030",
              "images": 121,
              "currency": "USD"
            },
            "by_day": [],
            "by_model": [],
            "by_status": [],
            "by_user": [
              {
                "user": "cust-42",
                "jobs": 61,
                "completed": 59,
                "failed": 1,
                "cancelled": 1,
                "charged": "11.294370",
                "images": 59
              },
              {
                "user": "cust-77",
                "jobs": 52,
                "completed": 48,
                "failed": 3,
                "cancelled": 1,
                "charged": "9.188640",
                "images": 48
              },
              {
                "user": null,
                "jobs": 15,
                "completed": 14,
                "failed": 1,
                "cancelled": 0,
                "charged": "2.680020",
                "images": 14
              }
            ],
            "by_api_key": [
              {
                "api_key_id": "key_1a2b3c4d5e6f708192a3b4c5",
                "api_key_prefix": "puz_live_3f9…",
                "jobs": 128,
                "completed": 121,
                "failed": 5,
                "cancelled": 2,
                "charged": "23.163030",
                "images": 121
              }
            ]
          }
        ]
      },
      "Balance": {
        "type": "object",
        "required": [
          "balance",
          "reserved",
          "currency",
          "status"
        ],
        "properties": {
          "balance": {
            "type": "number",
            "description": "Available balance, USD (2 decimals) — what new jobs can reserve",
            "example": 10
          },
          "reserved": {
            "type": "number",
            "description": "Held by queued / running jobs; released on failure or cancellation",
            "example": 0.19
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "credited",
              "unsettled"
            ],
            "description": "credited when balance > 0"
          }
        },
        "example": {
          "balance": 10,
          "reserved": 0.19,
          "currency": "USD",
          "status": "credited"
        }
      },
      "CatalogModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "live",
              "coming_soon"
            ]
          },
          "kinds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "text",
                "url"
              ]
            }
          },
          "aspect_ratios": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "1:1",
              "16:9",
              "9:16",
              "4:3"
            ]
          },
          "resolutions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "1K",
              "2K",
              "4K"
            ]
          },
          "prices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "text",
                    "url"
                  ]
                },
                "resolution": {
                  "type": "string"
                },
                "price_per_image": {
                  "type": "string",
                  "example": "0.191430"
                },
                "currency": {
                  "type": "string",
                  "enum": [
                    "USD"
                  ]
                }
              }
            }
          }
        }
      },
      "ModelCatalog": {
        "type": "object",
        "required": [
          "live",
          "coming_soon",
          "models"
        ],
        "properties": {
          "live": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "nano-banana-v2",
              "nano-banana-pro"
            ]
          },
          "coming_soon": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "omni",
              "veo",
              "gemini"
            ]
          },
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogModel"
            }
          }
        },
        "example": {
          "live": [
            "nano-banana-v2",
            "nano-banana-pro"
          ],
          "coming_soon": [
            "omni",
            "veo",
            "gemini"
          ],
          "models": [
            {
              "id": "nano-banana-pro",
              "status": "live",
              "kinds": [
                "text",
                "url"
              ],
              "aspect_ratios": [
                "1:1",
                "16:9",
                "9:16",
                "4:3"
              ],
              "resolutions": [
                "1K",
                "2K",
                "4K"
              ],
              "prices": [
                {
                  "kind": "text",
                  "resolution": "1K",
                  "price_per_image": "0.191430",
                  "currency": "USD"
                },
                {
                  "kind": "url",
                  "resolution": "1K",
                  "price_per_image": "0.191430",
                  "currency": "USD"
                }
              ]
            },
            {
              "id": "omni",
              "status": "coming_soon",
              "kinds": [],
              "aspect_ratios": [],
              "resolutions": [],
              "prices": []
            }
          ]
        }
      },
      "Pricing": {
        "type": "object",
        "required": [
          "currency",
          "unit",
          "effective_from",
          "models",
          "billing",
          "uploads"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ]
          },
          "unit": {
            "type": "string",
            "enum": [
              "image"
            ]
          },
          "effective_from": {
            "type": "string",
            "format": "date-time",
            "description": "Latest effective date across the active rows"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "model": {
                  "type": "string"
                },
                "prices": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "any",
                          "text",
                          "url"
                        ],
                        "description": "`any` = same price for text-to-image and image-to-image"
                      },
                      "resolution": {
                        "type": "string",
                        "enum": [
                          "1K",
                          "2K",
                          "4K"
                        ]
                      },
                      "price_per_image": {
                        "type": "string",
                        "description": "USD per generated image, 6 decimals",
                        "example": "0.191430"
                      },
                      "effective_from": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          },
          "billing": {
            "type": "object",
            "properties": {
              "reservation": {
                "type": "string"
              },
              "charge": {
                "type": "string"
              }
            }
          },
          "uploads": {
            "type": "object",
            "description": "File Upload API terms. price_per_upload and storage_price_per_gb_day of 0.000000 mean free. enabled false → POST /v1/uploads answers 503; existing files stay.",
            "required": [
              "enabled",
              "price_per_upload",
              "storage_price_per_gb_day",
              "currency",
              "max_file_bytes",
              "default_ttl_hours",
              "max_ttl_hours"
            ],
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "price_per_upload": {
                "type": "string",
                "description": "USD per accepted upload, 6 decimals"
              },
              "storage_price_per_gb_day": {
                "type": "string",
                "description": "USD per GiB of live uploads per UTC day"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "USD"
                ]
              },
              "max_file_bytes": {
                "type": "integer"
              },
              "default_ttl_hours": {
                "type": "integer"
              },
              "max_ttl_hours": {
                "type": "integer"
              }
            }
          }
        },
        "example": {
          "currency": "USD",
          "unit": "image",
          "effective_from": "2026-09-16T00:00:00.000Z",
          "models": [
            {
              "model": "nano-banana-pro",
              "prices": [
                {
                  "kind": "any",
                  "resolution": "1K",
                  "price_per_image": "0.191430",
                  "effective_from": "2026-09-16T00:00:00.000Z"
                },
                {
                  "kind": "any",
                  "resolution": "2K",
                  "price_per_image": "0.191430",
                  "effective_from": "2026-09-16T00:00:00.000Z"
                },
                {
                  "kind": "any",
                  "resolution": "4K",
                  "price_per_image": "0.342860",
                  "effective_from": "2026-09-16T00:00:00.000Z"
                }
              ]
            }
          ],
          "billing": {
            "reservation": "When a job is accepted, its worst-case price is moved from available to reserved balance.",
            "charge": "On completion the price for the delivered resolution is charged and any remainder is released; failed or cancelled jobs release the full reservation."
          },
          "uploads": {
            "enabled": true,
            "price_per_upload": "0.000000",
            "storage_price_per_gb_day": "0.000000",
            "currency": "USD",
            "max_file_bytes": 20971520,
            "default_ttl_hours": 24,
            "max_ttl_hours": 168
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "status",
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "status": {
                "type": "integer"
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_json",
                  "invalid_request",
                  "unauthorized",
                  "insufficient_funds",
                  "key_spend_limit_exceeded",
                  "model_not_allowed",
                  "output_forbidden",
                  "job_not_found",
                  "upload_not_found",
                  "output_not_found",
                  "method_not_allowed",
                  "idempotency_conflict",
                  "price_exceeds_max",
                  "job_not_cancellable",
                  "upload_quota_exceeded",
                  "output_expired",
                  "upload_too_large",
                  "unsupported_media_type",
                  "model_unavailable",
                  "rate_limited",
                  "internal_error",
                  "pricing_unavailable",
                  "uploads_disabled"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "description": "Same value as the X-Request-Id header",
                "example": "req_c4d5e6f708192a3b4c5d6e7f"
              }
            }
          }
        },
        "example": {
          "error": {
            "status": 402,
            "code": "insufficient_funds",
            "message": "Available balance $0.00 is below this request's price $0.19. Fund your balance in the console (USDC/USDT), or message Telegram @PUZZLE_Support_AI for a wire or contract.",
            "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Envelope POSTed to callback_url / the console endpoint. data.job is the Job object (result is null — fetch GET /v1/jobs/{id} for the link)",
        "required": [
          "id",
          "type",
          "version",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_7c8d9e0f1a2b3c4d5e6f7081",
            "description": "Same across retries of one event — de-duplicate on it"
          },
          "type": {
            "type": "string",
            "enum": [
              "job.queued",
              "job.completed",
              "job.failed",
              "job.cancelled"
            ]
          },
          "version": {
            "type": "string",
            "enum": [
              "2026-09-01"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "required": [
              "job"
            ],
            "properties": {
              "job": {
                "$ref": "#/components/schemas/Job"
              }
            }
          }
        },
        "example": {
          "id": "evt_7c8d9e0f1a2b3c4d5e6f7081",
          "type": "job.completed",
          "version": "2026-09-01",
          "created_at": "2026-09-17T12:00:09.000Z",
          "data": {
            "job": {
              "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
              "request_id": "req_9a1b2c3d4e5f60718293a4b5",
              "status": "completed",
              "model": "nano-banana-pro",
              "kind": "text",
              "user": "cust-42",
              "settings": {
                "aspect_ratio": "1:1",
                "resolution": "1K"
              },
              "price": {
                "estimated_max": "0.191430",
                "charged": "0.191430",
                "currency": "USD"
              },
              "result": null,
              "error": null,
              "created_at": "2026-09-17T12:00:00.000Z",
              "started_at": "2026-09-17T12:00:02.000Z",
              "completed_at": "2026-09-17T12:00:09.000Z",
              "failed_at": null
            }
          }
        }
      },
      "Upload": {
        "type": "object",
        "description": "A live customer source image (`POST /v1/uploads`). url is a signed /v1/outputs link.",
        "required": [
          "id",
          "url",
          "mime_type",
          "size_bytes",
          "width",
          "height",
          "created_at",
          "expires_at",
          "user"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
            "description": "`upl_…`"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed GET /v1/outputs/{key}?exp&sig, valid until expires_at"
          },
          "mime_type": {
            "type": "string",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ]
          },
          "size_bytes": {
            "type": "integer"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "user": {
            "type": [
              "string",
              "null"
            ]
          },
          "storage_warning": {
            "type": "string",
            "description": "Present when the workspace cannot cover the next daily storage fee; unpaid days shorten expiry to 24 h"
          }
        },
        "example": {
          "id": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
          "url": "https://api.puzzle-cloud.com/v1/outputs/uploads/2026/09/upl_4a5b6c7d8e9f0a1b2c3d4e5f.png?exp=1789732800&sig=p7QmN2w8R5cYv0KsT3hUaL6nX9dEiB1fZrWjGo",
          "mime_type": "image/png",
          "size_bytes": 214,
          "width": 1,
          "height": 1,
          "created_at": "2026-09-17T12:00:00.000Z",
          "expires_at": "2026-09-18T12:00:00.000Z",
          "user": "cust-42"
        }
      },
      "UploadsPage": {
        "type": "object",
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Upload"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "example": {
          "data": [
            {
              "id": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
              "url": "https://api.puzzle-cloud.com/v1/outputs/uploads/2026/09/upl_4a5b6c7d8e9f0a1b2c3d4e5f.png?exp=1789732800&sig=p7QmN2w8R5cYv0KsT3hUaL6nX9dEiB1fZrWjGo",
              "mime_type": "image/png",
              "size_bytes": 214,
              "width": 1,
              "height": 1,
              "created_at": "2026-09-17T12:00:00.000Z",
              "expires_at": "2026-09-18T12:00:00.000Z",
              "user": "cust-42"
            }
          ],
          "has_more": false,
          "next_cursor": null
        }
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    },
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/text-generations": {
      "post": {
        "summary": "Create text-to-image generation",
        "description": "Validates, prices from the active list, reserves the worst-case price and queues the job in one step. Counts toward the workspace rate limit.",
        "operationId": "createTextGeneration",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model",
                  "prompt"
                ],
                "properties": {
                  "model": {
                    "type": "string",
                    "enum": [
                      "nano-banana-v2",
                      "nano-banana-pro"
                    ],
                    "description": "Unknown id → 400; omni, veo, gemini → 422 model_unavailable"
                  },
                  "prompt": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "aspect_ratio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "16:9",
                      "9:16",
                      "4:3"
                    ]
                  },
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "1K",
                      "2K",
                      "4K"
                    ],
                    "default": "1K",
                    "description": "Sets the price — see POST /v1/quote"
                  },
                  "callback_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS webhook for this job (public address). Receives job.queued, job.completed, job.failed, job.cancelled"
                  },
                  "user": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your end-user id, stored verbatim. Returned on the job; filter in GET /v1/jobs; group_by in GET /v1/usage"
                  },
                  "max_price": {
                    "type": "string",
                    "pattern": "^\\d+(\\.\\d{1,6})?$",
                    "description": "USD ceiling (> 0). Quoted price above it → 409 price_exceeds_max, nothing charged",
                    "example": "0.25"
                  }
                }
              },
              "example": {
                "model": "nano-banana-pro",
                "prompt": "A cyan puzzle piece, studio light",
                "aspect_ratio": "1:1",
                "resolution": "1K",
                "user": "cust-42",
                "max_price": "0.25"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted and queued (the same object GET /v1/jobs/{id} returns)",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                  "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                  "status": "queued",
                  "model": "nano-banana-pro",
                  "kind": "text",
                  "user": "cust-42",
                  "settings": {
                    "aspect_ratio": "1:1",
                    "resolution": "1K"
                  },
                  "price": {
                    "estimated_max": "0.191430",
                    "charged": null,
                    "currency": "USD"
                  },
                  "result": null,
                  "error": null,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "started_at": null,
                  "completed_at": null,
                  "failed_at": null
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json` / `invalid_request` — body not JSON, or a field / Idempotency-Key failed validation",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 400,
                    "code": "invalid_request",
                    "message": "Field `prompt` is required.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — missing, malformed, unknown or revoked key",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 401,
                    "code": "unauthorized",
                    "message": "Missing or invalid API key.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_funds` / `key_spend_limit_exceeded` — nothing charged",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 402,
                    "code": "insufficient_funds",
                    "message": "Available balance $0.00 is below this request's price $0.19. Fund your balance in the console (USDC/USDT), or message Telegram @PUZZLE_Support_AI for a wire or contract.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "403": {
            "description": "`model_not_allowed` — the key's allowed-models list excludes this model",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 403,
                    "code": "model_not_allowed",
                    "message": "This API key may not use nano-banana-pro. Allowed: nano-banana-v2. Change the key's allowed models in the console.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`price_exceeds_max` / `idempotency_conflict`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 409,
                    "code": "price_exceeds_max",
                    "message": "Quoted price $0.342860 exceeds max_price $0.250000 for nano-banana-pro @ 4K. Raise max_price or pick a lower resolution; nothing was charged.",
                    "request_id": "req_e6f708192a3b4c5d6e7f8091"
                  }
                }
              }
            }
          },
          "422": {
            "description": "`model_unavailable` — announced model, not served yet",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 422,
                    "code": "model_unavailable",
                    "message": "veo is not served yet.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — workspace requests-per-minute limit reached",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 429,
                    "code": "rate_limited",
                    "message": "Workspace rate limit is 60 requests per minute. Retry in 17s.",
                    "request_id": "req_d5e6f708192a3b4c5d6e7f80"
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "`pricing_unavailable` — no active price for this model and resolution",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 503,
                    "code": "pricing_unavailable",
                    "message": "No active price for this model and resolution. Try again shortly.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/url-generations": {
      "post": {
        "summary": "Create image-to-image generation",
        "description": "Same as text-generations plus a source: public HTTPS `url` (alias `image_url`) or `upload_id` from POST /v1/uploads (send one, not both). A public URL is fetched at run time (image/png, image/jpeg, image/webp, image/gif, image/heic, image/heif, ≤ 20 MB). Counts toward the workspace rate limit.",
        "operationId": "createUrlGeneration",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model",
                  "prompt"
                ],
                "properties": {
                  "model": {
                    "type": "string",
                    "enum": [
                      "nano-banana-v2",
                      "nano-banana-pro"
                    ]
                  },
                  "prompt": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS source image. Alias: image_url (url wins when both are sent). Omit when sending upload_id"
                  },
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Alias for url"
                  },
                  "upload_id": {
                    "type": "string",
                    "description": "Live upload id (`upl_…`) from POST /v1/uploads. Send either url or upload_id, not both",
                    "example": "upl_4a5b6c7d8e9f0a1b2c3d4e5f"
                  },
                  "aspect_ratio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "16:9",
                      "9:16",
                      "4:3"
                    ]
                  },
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "1K",
                      "2K",
                      "4K"
                    ],
                    "default": "1K"
                  },
                  "callback_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS webhook for this job (public address). Receives job.queued, job.completed, job.failed, job.cancelled"
                  },
                  "user": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your end-user id, stored verbatim. Returned on the job; filter in GET /v1/jobs; group_by in GET /v1/usage"
                  },
                  "max_price": {
                    "type": "string",
                    "pattern": "^\\d+(\\.\\d{1,6})?$",
                    "description": "USD ceiling (> 0). Quoted price above it → 409 price_exceeds_max, nothing charged",
                    "example": "0.25"
                  }
                }
              },
              "example": {
                "model": "nano-banana-pro",
                "prompt": "Restyle as cyan studio light, keep the composition",
                "url": "https://example.com/source.png",
                "resolution": "2K",
                "callback_url": "https://example.com/puzzle/webhook"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted and queued (the same object GET /v1/jobs/{id} returns)",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                  "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                  "status": "queued",
                  "model": "nano-banana-pro",
                  "kind": "url",
                  "user": null,
                  "settings": {
                    "resolution": "2K"
                  },
                  "price": {
                    "estimated_max": "0.191430",
                    "charged": null,
                    "currency": "USD"
                  },
                  "result": null,
                  "error": null,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "started_at": null,
                  "completed_at": null,
                  "failed_at": null
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json` / `invalid_request` — body not JSON, or a field / Idempotency-Key failed validation",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 400,
                    "code": "invalid_request",
                    "message": "Field `prompt` is required.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — missing, malformed, unknown or revoked key",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 401,
                    "code": "unauthorized",
                    "message": "Missing or invalid API key.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_funds` / `key_spend_limit_exceeded` — nothing charged",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 402,
                    "code": "insufficient_funds",
                    "message": "Available balance $0.00 is below this request's price $0.19. Fund your balance in the console (USDC/USDT), or message Telegram @PUZZLE_Support_AI for a wire or contract.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "403": {
            "description": "`model_not_allowed` — the key's allowed-models list excludes this model",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 403,
                    "code": "model_not_allowed",
                    "message": "This API key may not use nano-banana-pro. Allowed: nano-banana-v2. Change the key's allowed models in the console.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`price_exceeds_max` / `idempotency_conflict`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 409,
                    "code": "price_exceeds_max",
                    "message": "Quoted price $0.342860 exceeds max_price $0.250000 for nano-banana-pro @ 4K. Raise max_price or pick a lower resolution; nothing was charged.",
                    "request_id": "req_e6f708192a3b4c5d6e7f8091"
                  }
                }
              }
            }
          },
          "422": {
            "description": "`model_unavailable` — announced model, not served yet",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 422,
                    "code": "model_unavailable",
                    "message": "veo is not served yet.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — workspace requests-per-minute limit reached",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 429,
                    "code": "rate_limited",
                    "message": "Workspace rate limit is 60 requests per minute. Retry in 17s.",
                    "request_id": "req_d5e6f708192a3b4c5d6e7f80"
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "`pricing_unavailable` — no active price for this model and resolution",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 503,
                    "code": "pricing_unavailable",
                    "message": "No active price for this model and resolution. Try again shortly.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "summary": "Store a source image",
        "description": "Multipart `file`, JSON base64 `data`, or JSON public https `url`. Sniffed as image/png, image/jpeg, image/webp. Counts toward the workspace rate limit. Operator kill switch → 503 uploads_disabled (existing files stay).",
        "operationId": "createUpload",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PNG, JPEG or WebP. Magic bytes are sniffed; Content-Type is ignored"
                  },
                  "ttl_hours": {
                    "type": "integer",
                    "description": "Requested lifetime in hours; omitted → platform default"
                  },
                  "user": {
                    "type": "string",
                    "maxLength": 128
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "string",
                    "description": "Base64-encoded image bytes (`data:` URLs accepted)"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public https URL Puzzle fetches (SSRF-pinned)"
                  },
                  "mime_type": {
                    "type": "string",
                    "description": "Informational with `data`; the sniffer decides"
                  },
                  "ttl_hours": {
                    "type": "integer"
                  },
                  "user": {
                    "type": "string",
                    "maxLength": 128
                  }
                }
              },
              "example": {
                "data": "<base64 png>",
                "mime_type": "image/png",
                "user": "cust-42",
                "ttl_hours": 24
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload stored",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                },
                "example": {
                  "id": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
                  "url": "https://api.puzzle-cloud.com/v1/outputs/uploads/2026/09/upl_4a5b6c7d8e9f0a1b2c3d4e5f.png?exp=1789732800&sig=p7QmN2w8R5cYv0KsT3hUaL6nX9dEiB1fZrWjGo",
                  "mime_type": "image/png",
                  "size_bytes": 214,
                  "width": 1,
                  "height": 1,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "expires_at": "2026-09-18T12:00:00.000Z",
                  "user": "cust-42"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json` / `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_funds` — upload fee exceeds available balance",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`upload_quota_exceeded` — workspace file-count or byte quota",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 409,
                    "code": "upload_quota_exceeded",
                    "message": "This workspace already holds 100 of 100 uploads. Delete some or wait for them to expire.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "413": {
            "description": "`upload_too_large` — over max_file_bytes",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 413,
                    "code": "upload_too_large",
                    "message": "File exceeds the 20971520-byte limit.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type` — not a sniffed PNG / JPEG / WebP",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 415,
                    "code": "unsupported_media_type",
                    "message": "Unsupported image type. Accepted: image/png, image/jpeg, image/webp.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — workspace requests-per-minute limit reached",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 429,
                    "code": "rate_limited",
                    "message": "Workspace rate limit is 60 requests per minute. Retry in 17s.",
                    "request_id": "req_d5e6f708192a3b4c5d6e7f80"
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "`uploads_disabled` — operator kill switch; existing files stay",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 503,
                    "code": "uploads_disabled",
                    "message": "Uploads are temporarily disabled. Existing uploads stay available; pass a public `url` instead.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List live uploads",
        "description": "Live uploads of the workspace, newest first, keyset-paginated on (created_at, id). Does not count toward the rate limit.",
        "operationId": "listUploads",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1–100, default 20",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque `next_cursor` of the previous page",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of uploads",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadsPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
                      "url": "https://api.puzzle-cloud.com/v1/outputs/uploads/2026/09/upl_4a5b6c7d8e9f0a1b2c3d4e5f.png?exp=1789732800&sig=p7QmN2w8R5cYv0KsT3hUaL6nX9dEiB1fZrWjGo",
                      "mime_type": "image/png",
                      "size_bytes": 214,
                      "width": 1,
                      "height": 1,
                      "created_at": "2026-09-17T12:00:00.000Z",
                      "expires_at": "2026-09-18T12:00:00.000Z",
                      "user": "cust-42"
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — bad limit or cursor",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads/{id}": {
      "get": {
        "summary": "Get one upload",
        "description": "A live upload of this workspace with a freshly signed url. Deleted, expired or purged ids → 404 upload_not_found.",
        "operationId": "getUpload",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Upload id (`upl_…`)",
            "schema": {
              "type": "string"
            },
            "example": "upl_4a5b6c7d8e9f0a1b2c3d4e5f"
          }
        ],
        "responses": {
          "200": {
            "description": "Upload",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                },
                "example": {
                  "id": "upl_4a5b6c7d8e9f0a1b2c3d4e5f",
                  "url": "https://api.puzzle-cloud.com/v1/outputs/uploads/2026/09/upl_4a5b6c7d8e9f0a1b2c3d4e5f.png?exp=1789732800&sig=p7QmN2w8R5cYv0KsT3hUaL6nX9dEiB1fZrWjGo",
                  "mime_type": "image/png",
                  "size_bytes": 214,
                  "width": 1,
                  "height": 1,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "expires_at": "2026-09-18T12:00:00.000Z",
                  "user": "cust-42"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`upload_not_found` — deleted, expired, purged or never existed",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 404,
                    "code": "upload_not_found",
                    "message": "No upload with that id in your workspace (deleted, expired or never existed).",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete an upload",
        "description": "Soft-deletes the row and removes the object. It no longer counts toward the workspace quota. 204, no body.",
        "operationId": "deleteUpload",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Upload id (`upl_…`)",
            "schema": {
              "type": "string"
            },
            "example": "upl_4a5b6c7d8e9f0a1b2c3d4e5f"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`upload_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quote": {
      "post": {
        "summary": "Price a request without creating a job",
        "description": "Returns the worst-case price a generation with these parameters would reserve (= price.estimated_max). Nothing is reserved or charged; does not count toward the rate limit.",
        "operationId": "quote",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model"
                ],
                "properties": {
                  "model": {
                    "type": "string",
                    "enum": [
                      "nano-banana-v2",
                      "nano-banana-pro"
                    ]
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "text",
                      "url"
                    ],
                    "default": "text"
                  },
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "1K",
                      "2K",
                      "4K"
                    ],
                    "default": "1K"
                  },
                  "aspect_ratio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "16:9",
                      "9:16",
                      "4:3"
                    ],
                    "description": "Accepted for symmetry; does not affect the price"
                  }
                }
              },
              "example": {
                "model": "nano-banana-pro",
                "kind": "text",
                "resolution": "2K"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "model": "nano-banana-pro",
                  "kind": "text",
                  "resolution": "2K",
                  "estimated_max": "0.191430",
                  "currency": "USD",
                  "pricing_version": "pv_5e6f70819a2b3c4d5e6f7081"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json` / `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "`model_unavailable` — announced model, not served yet",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "`pricing_unavailable` — no active price for this model and resolution",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/jobs": {
      "get": {
        "summary": "List jobs (newest first, cursor-paginated)",
        "description": "Jobs of the key's workspace. Pass next_cursor back as cursor until has_more is false. The list omits result (null); fetch GET /v1/jobs/{id} for a download link.",
        "operationId": "listJobs",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "queued, processing, retry_scheduled, completed, failed, cancelled",
            "schema": {
              "$ref": "#/components/schemas/JobStatus"
            }
          },
          {
            "name": "model",
            "in": "query",
            "required": false,
            "description": "Exact model id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user",
            "in": "query",
            "required": false,
            "description": "Jobs created with this `user` value",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "description": "ISO 8601 date-time, inclusive",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "required": false,
            "description": "ISO 8601 date-time, exclusive",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1–100, default 20",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque `next_cursor` of the previous page",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of jobs",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobsPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                      "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                      "status": "completed",
                      "model": "nano-banana-pro",
                      "kind": "text",
                      "user": "cust-42",
                      "settings": {
                        "aspect_ratio": "1:1",
                        "resolution": "1K"
                      },
                      "price": {
                        "estimated_max": "0.191430",
                        "charged": "0.191430",
                        "currency": "USD"
                      },
                      "result": null,
                      "error": null,
                      "created_at": "2026-09-17T12:00:00.000Z",
                      "started_at": "2026-09-17T12:00:02.000Z",
                      "completed_at": "2026-09-17T12:00:09.000Z",
                      "failed_at": null
                    },
                    {
                      "id": "job_7d21c0aa9e4b5f3c8d6e1f02",
                      "request_id": "req_0b1c2d3e4f5061728394a5b6",
                      "status": "failed",
                      "model": "nano-banana-pro",
                      "kind": "text",
                      "user": "cust-42",
                      "settings": {
                        "aspect_ratio": "1:1",
                        "resolution": "1K"
                      },
                      "price": {
                        "estimated_max": "0.191430",
                        "charged": null,
                        "currency": "USD"
                      },
                      "result": null,
                      "error": {
                        "code": "provider_server",
                        "message": "The generation backend failed. Retries exhausted; you were not charged."
                      },
                      "created_at": "2026-09-17T11:58:12.000Z",
                      "started_at": "2026-09-17T11:58:13.000Z",
                      "completed_at": null,
                      "failed_at": "2026-09-17T12:07:41.000Z"
                    }
                  ],
                  "has_more": true,
                  "next_cursor": "eyJjIjoiMjAyNi0wOS0xNyAxMTo1ODoxMi4wMDAwMDArMDAiLCJpIjoiam9iXzdkMjFjMGFhOWU0YjVmM2M4ZDZlMWYwMiJ9"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — bad query parameter or cursor",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "summary": "Get job status",
        "description": "Statuses: queued, processing, retry_scheduled, completed, failed, cancelled. Completed jobs carry result (signed download link, valid until result.expires_at); failed jobs carry error and are not charged.",
        "operationId": "getJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Job id (`job_…`)",
            "schema": {
              "type": "string"
            },
            "example": "job_3f9c2b7a1d4e8f60a1b2c3d4"
          }
        ],
        "responses": {
          "200": {
            "description": "Job",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                  "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                  "status": "completed",
                  "model": "nano-banana-pro",
                  "kind": "text",
                  "user": "cust-42",
                  "settings": {
                    "aspect_ratio": "1:1",
                    "resolution": "1K"
                  },
                  "price": {
                    "estimated_max": "0.191430",
                    "charged": "0.191430",
                    "currency": "USD"
                  },
                  "result": {
                    "url": "https://api.puzzle-cloud.com/v1/outputs/2026/09/job_3f9c2b7a1d4e8f60a1b2c3d4.png?exp=1790251209&sig=k2VhO3s9Q6bXw1LpR8dTzN4mY7cFgJ0eAqUiHvKo",
                    "mime_type": "image/png",
                    "width": 1024,
                    "height": 1024,
                    "expires_at": "2026-09-24T12:00:09.000Z"
                  },
                  "error": null,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "started_at": "2026-09-17T12:00:02.000Z",
                  "completed_at": "2026-09-17T12:00:09.000Z",
                  "failed_at": null
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`job_not_found` — no job with that id in your workspace",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 404,
                    "code": "job_not_found",
                    "message": "No job with that id. Jobs are created only after a paid generation is accepted.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/jobs/{id}/cancel": {
      "post": {
        "summary": "Cancel a queued job",
        "description": "Only jobs still in `queued` can be cancelled; the reservation is released and a job.cancelled event is sent. Processing, retry_scheduled or finished jobs answer 409.",
        "operationId": "cancelJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Job id (`job_…`)",
            "schema": {
              "type": "string"
            },
            "example": "job_3f9c2b7a1d4e8f60a1b2c3d4"
          }
        ],
        "responses": {
          "200": {
            "description": "Job with status cancelled",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                  "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                  "status": "cancelled",
                  "model": "nano-banana-pro",
                  "kind": "text",
                  "user": "cust-42",
                  "settings": {
                    "aspect_ratio": "1:1",
                    "resolution": "1K"
                  },
                  "price": {
                    "estimated_max": "0.191430",
                    "charged": null,
                    "currency": "USD"
                  },
                  "result": null,
                  "error": null,
                  "created_at": "2026-09-17T12:00:00.000Z",
                  "started_at": null,
                  "completed_at": null,
                  "failed_at": null
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`job_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`job_not_cancellable` — job already started or finished",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 409,
                    "code": "job_not_cancellable",
                    "message": "Job is processing; only queued jobs can be cancelled.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "summary": "Usage and spend aggregates",
        "description": "Totals over a range of UTC calendar days (default: the last 30 days) plus optional breakdowns. Unrequested breakdowns are empty arrays.",
        "operationId": "getUsage",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD (UTC), inclusive. Default: 29 days before end_date",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD (UTC), inclusive. Default: today. Span ≤ 366 days",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "group_by",
            "in": "query",
            "required": false,
            "description": "Repeatable or comma-separated: day, model, status, user, api_key. Unselected breakdowns come back empty",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "day",
                  "model",
                  "status",
                  "user",
                  "api_key"
                ]
              }
            },
            "explode": true
          }
        ],
        "responses": {
          "200": {
            "description": "Usage report",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                },
                "example": {
                  "period": {
                    "start": "2026-09-01",
                    "end": "2026-09-30",
                    "timezone": "UTC"
                  },
                  "totals": {
                    "jobs": 128,
                    "completed": 121,
                    "failed": 5,
                    "cancelled": 2,
                    "charged": "23.163030",
                    "images": 121,
                    "currency": "USD"
                  },
                  "by_day": [
                    {
                      "day": "2026-09-01",
                      "jobs": 4,
                      "completed": 4,
                      "failed": 0,
                      "cancelled": 0,
                      "charged": "0.765720",
                      "images": 4
                    },
                    {
                      "day": "2026-09-02",
                      "jobs": 7,
                      "completed": 6,
                      "failed": 1,
                      "cancelled": 0,
                      "charged": "1.148580",
                      "images": 6
                    }
                  ],
                  "by_model": [
                    {
                      "model": "nano-banana-pro",
                      "jobs": 90,
                      "completed": 86,
                      "failed": 3,
                      "cancelled": 1,
                      "charged": "16.471980",
                      "images": 86
                    },
                    {
                      "model": "nano-banana-v2",
                      "jobs": 38,
                      "completed": 35,
                      "failed": 2,
                      "cancelled": 1,
                      "charged": "6.691050",
                      "images": 35
                    }
                  ],
                  "by_status": [],
                  "by_user": [],
                  "by_api_key": []
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — invalid date, span over 366 days, or unknown group_by",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "summary": "Available and reserved balance",
        "description": "balance = available (what new jobs can reserve); reserved = held by queued / running jobs; status = credited when balance > 0, else unsettled.",
        "operationId": "getBalance",
        "responses": {
          "200": {
            "description": "Current balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance": 10,
                  "reserved": 0.19,
                  "currency": "USD",
                  "status": "credited"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/models": {
      "get": {
        "summary": "Model catalog",
        "description": "Public. `live` / `coming_soon` are id lists; `models` adds per-model parameters and current prices from the active price list. Cache-Control: public, max-age=60.",
        "operationId": "listModels",
        "security": [],
        "responses": {
          "200": {
            "description": "Model catalog",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelCatalog"
                },
                "example": {
                  "live": [
                    "nano-banana-v2",
                    "nano-banana-pro"
                  ],
                  "coming_soon": [
                    "omni",
                    "veo",
                    "gemini"
                  ],
                  "models": [
                    {
                      "id": "nano-banana-pro",
                      "status": "live",
                      "kinds": [
                        "text",
                        "url"
                      ],
                      "aspect_ratios": [
                        "1:1",
                        "16:9",
                        "9:16",
                        "4:3"
                      ],
                      "resolutions": [
                        "1K",
                        "2K",
                        "4K"
                      ],
                      "prices": [
                        {
                          "kind": "text",
                          "resolution": "1K",
                          "price_per_image": "0.191430",
                          "currency": "USD"
                        },
                        {
                          "kind": "url",
                          "resolution": "1K",
                          "price_per_image": "0.191430",
                          "currency": "USD"
                        }
                      ]
                    },
                    {
                      "id": "omni",
                      "status": "coming_soon",
                      "kinds": [],
                      "aspect_ratios": [],
                      "resolutions": [],
                      "prices": []
                    }
                  ]
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pricing": {
      "get": {
        "summary": "Active price list",
        "description": "Public. USD per generated image by model, mode and resolution, plus the effective date and the reserve → charge rules. Same numbers as https://puzzle-cloud.com/pricing. Cache-Control: public, max-age=60.",
        "operationId": "getPricing",
        "security": [],
        "responses": {
          "200": {
            "description": "Price list",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                },
                "example": {
                  "currency": "USD",
                  "unit": "image",
                  "effective_from": "2026-09-16T00:00:00.000Z",
                  "models": [
                    {
                      "model": "nano-banana-pro",
                      "prices": [
                        {
                          "kind": "any",
                          "resolution": "1K",
                          "price_per_image": "0.191430",
                          "effective_from": "2026-09-16T00:00:00.000Z"
                        },
                        {
                          "kind": "any",
                          "resolution": "2K",
                          "price_per_image": "0.191430",
                          "effective_from": "2026-09-16T00:00:00.000Z"
                        },
                        {
                          "kind": "any",
                          "resolution": "4K",
                          "price_per_image": "0.342860",
                          "effective_from": "2026-09-16T00:00:00.000Z"
                        }
                      ]
                    }
                  ],
                  "billing": {
                    "reservation": "When a job is accepted, its worst-case price is moved from available to reserved balance.",
                    "charge": "On completion the price for the delivered resolution is charged and any remainder is released; failed or cancelled jobs release the full reservation."
                  },
                  "uploads": {
                    "enabled": true,
                    "price_per_upload": "0.000000",
                    "storage_price_per_gb_day": "0.000000",
                    "currency": "USD",
                    "max_file_bytes": 20971520,
                    "default_ttl_hours": 24,
                    "max_ttl_hours": 168
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "`pricing_unavailable` — no active price list",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/outputs/{key}": {
      "get": {
        "summary": "Download a generated image",
        "description": "Target of result.url. No API key: the exp/sig query pair is the credential. Valid until result.expires_at (7 days after completion on current storage).",
        "operationId": "getOutput",
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Object key from result.url, e.g. 2026/09/job_….png",
            "schema": {
              "type": "string"
            },
            "example": "2026/09/job_3f9c2b7a1d4e8f60a1b2c3d4.png"
          },
          {
            "name": "exp",
            "in": "query",
            "required": true,
            "description": "Expiry, Unix seconds",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sig",
            "in": "query",
            "required": true,
            "description": "HMAC signature issued by Puzzle",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "302": {
            "description": "Redirect to object storage (when the output lives in a bucket)"
          },
          "403": {
            "description": "`output_forbidden` — missing or invalid signature",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 403,
                    "code": "output_forbidden",
                    "message": "Invalid download signature.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "404": {
            "description": "`output_not_found` — output no longer stored",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 404,
                    "code": "output_not_found",
                    "message": "This output is no longer available.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "410": {
            "description": "`output_expired` — link expired",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": {
                    "status": 410,
                    "code": "output_expired",
                    "message": "This download link has expired. Fetch the job again for a fresh link.",
                    "request_id": "req_c4d5e6f708192a3b4c5d6e7f"
                  }
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "head": {
        "summary": "Headers of a generated image",
        "description": "Same as GET without the body.",
        "operationId": "headOutput",
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exp",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sig",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Content-Type, Content-Length and Cache-Control of the image",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            }
          },
          "403": {
            "description": "`output_forbidden`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`output_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "410": {
            "description": "`output_expired`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "job.queued": {
      "post": {
        "summary": "job.queued — POSTed to callback_url",
        "description": "Signed with X-Puzzle-Signature = sha256 HMAC over \"<X-Puzzle-Timestamp>.<body>\" when a webhook secret exists. Retried after 30 s, 2 min, 10 min, 1 h, 6 h, 24 h unless you answer 2xx within 10 s.",
        "parameters": [
          {
            "name": "X-Puzzle-Signature",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` — HMAC-SHA256 with your webhook secret over `<X-Puzzle-Timestamp>.<raw body>`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds when the attempt was sent; reject deliveries older than your tolerance to stop replays",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Signature-Legacy",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` over the raw body only — for receivers written before timestamps; prefer X-Puzzle-Signature",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Id",
            "in": "header",
            "required": true,
            "description": "`evt_…` — same across retries of one event; use it to de-duplicate",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Type",
            "in": "header",
            "required": true,
            "description": "job.queued, job.completed, job.failed, job.cancelled",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Version",
            "in": "header",
            "required": true,
            "description": "Envelope version, currently 2026-09-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Delivery-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2 … 7",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": true,
            "description": "`Puzzle-Webhooks/1`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged — no retry"
          }
        }
      }
    },
    "job.completed": {
      "post": {
        "summary": "job.completed — POSTed to callback_url and the console endpoint",
        "description": "Signed with X-Puzzle-Signature = sha256 HMAC over \"<X-Puzzle-Timestamp>.<body>\" when a webhook secret exists. Retried after 30 s, 2 min, 10 min, 1 h, 6 h, 24 h unless you answer 2xx within 10 s.",
        "parameters": [
          {
            "name": "X-Puzzle-Signature",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` — HMAC-SHA256 with your webhook secret over `<X-Puzzle-Timestamp>.<raw body>`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds when the attempt was sent; reject deliveries older than your tolerance to stop replays",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Signature-Legacy",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` over the raw body only — for receivers written before timestamps; prefer X-Puzzle-Signature",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Id",
            "in": "header",
            "required": true,
            "description": "`evt_…` — same across retries of one event; use it to de-duplicate",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Type",
            "in": "header",
            "required": true,
            "description": "job.queued, job.completed, job.failed, job.cancelled",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Version",
            "in": "header",
            "required": true,
            "description": "Envelope version, currently 2026-09-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Delivery-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2 … 7",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": true,
            "description": "`Puzzle-Webhooks/1`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_7c8d9e0f1a2b3c4d5e6f7081",
                "type": "job.completed",
                "version": "2026-09-01",
                "created_at": "2026-09-17T12:00:09.000Z",
                "data": {
                  "job": {
                    "id": "job_3f9c2b7a1d4e8f60a1b2c3d4",
                    "request_id": "req_9a1b2c3d4e5f60718293a4b5",
                    "status": "completed",
                    "model": "nano-banana-pro",
                    "kind": "text",
                    "user": "cust-42",
                    "settings": {
                      "aspect_ratio": "1:1",
                      "resolution": "1K"
                    },
                    "price": {
                      "estimated_max": "0.191430",
                      "charged": "0.191430",
                      "currency": "USD"
                    },
                    "result": null,
                    "error": null,
                    "created_at": "2026-09-17T12:00:00.000Z",
                    "started_at": "2026-09-17T12:00:02.000Z",
                    "completed_at": "2026-09-17T12:00:09.000Z",
                    "failed_at": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged — no retry"
          }
        }
      }
    },
    "job.failed": {
      "post": {
        "summary": "job.failed — POSTed to callback_url and the console endpoint",
        "description": "Signed with X-Puzzle-Signature = sha256 HMAC over \"<X-Puzzle-Timestamp>.<body>\" when a webhook secret exists. Retried after 30 s, 2 min, 10 min, 1 h, 6 h, 24 h unless you answer 2xx within 10 s.",
        "parameters": [
          {
            "name": "X-Puzzle-Signature",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` — HMAC-SHA256 with your webhook secret over `<X-Puzzle-Timestamp>.<raw body>`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds when the attempt was sent; reject deliveries older than your tolerance to stop replays",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Signature-Legacy",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` over the raw body only — for receivers written before timestamps; prefer X-Puzzle-Signature",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Id",
            "in": "header",
            "required": true,
            "description": "`evt_…` — same across retries of one event; use it to de-duplicate",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Type",
            "in": "header",
            "required": true,
            "description": "job.queued, job.completed, job.failed, job.cancelled",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Version",
            "in": "header",
            "required": true,
            "description": "Envelope version, currently 2026-09-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Delivery-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2 … 7",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": true,
            "description": "`Puzzle-Webhooks/1`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged — no retry"
          }
        }
      }
    },
    "job.cancelled": {
      "post": {
        "summary": "job.cancelled — POSTed to callback_url",
        "description": "Signed with X-Puzzle-Signature = sha256 HMAC over \"<X-Puzzle-Timestamp>.<body>\" when a webhook secret exists. Retried after 30 s, 2 min, 10 min, 1 h, 6 h, 24 h unless you answer 2xx within 10 s.",
        "parameters": [
          {
            "name": "X-Puzzle-Signature",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` — HMAC-SHA256 with your webhook secret over `<X-Puzzle-Timestamp>.<raw body>`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds when the attempt was sent; reject deliveries older than your tolerance to stop replays",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Signature-Legacy",
            "in": "header",
            "required": false,
            "description": "`sha256=<hex>` over the raw body only — for receivers written before timestamps; prefer X-Puzzle-Signature",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Id",
            "in": "header",
            "required": true,
            "description": "`evt_…` — same across retries of one event; use it to de-duplicate",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Type",
            "in": "header",
            "required": true,
            "description": "job.queued, job.completed, job.failed, job.cancelled",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Event-Version",
            "in": "header",
            "required": true,
            "description": "Envelope version, currently 2026-09-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Puzzle-Delivery-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2 … 7",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": true,
            "description": "`Puzzle-Webhooks/1`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged — no retry"
          }
        }
      }
    }
  }
}
