{
  "openapi": "3.1.0",
  "info": {
    "title": "x402 Image API",
    "version": "1.0.0",
    "description": "Paid image generation on the x402 protocol (USDC on Base mainnet via PayAI). POST /generate with {prompt} â†’ 402 PAYMENT-REQUIRED â†’ sign EIP-3009 â†’ retry with PAYMENT-SIGNATURE â†’ 202 {jobId, pollUrl} â†’ poll GET /result/:id until done â†’ white-label PNGs at /images/*. Cost: low:1K `$0.005/image (total = per_image Ã— count). self-hosted diffusion, 202+polling avoids Cloudflare 100s, jobs persisted 24h. Live at https://x402.chaly.org",
    "contact": {
      "url": "https://x402.chaly.org",
      "name": "x402-image-api"
    },
    "license": {
      "name": "Private"
    }
  },
  "servers": [
    {
      "url": "https://x402.chaly.org",
      "description": "Production (Cloudflare Tunnel, mainnet eip155:8453)"
    },
    {
      "url": "http://localhost:4021",
      "description": "Local dev"
    }
  ],
  "tags": [
    { "name": "generate", "description": "Paid image generation (x402)" },
    { "name": "jobs", "description": "Polling + white-label images" },
    { "name": "health", "description": "Probes + metrics" },
    { "name": "x402", "description": "Protocol discovery" }
  ],
  "paths": {
    "/generate": {
      "post": {
        "tags": ["generate"],
        "summary": "Generate images (x402 paid)",
        "description": "Paid endpoint. Unpaid â†’ 402 with PAYMENT-REQUIRED header (base64 x402 offer: network eip155:8453, asset 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 USDC, payTo 0x53406C32dF6e494aFB078279EA3AD752542E5F0f, amount 5000 = `$0.005 per image Ã— count, maxTimeoutSeconds 900). Sign EIP-3009 with PAYMENT-SIGNATURE header â†’ 202 {jobId, pollUrl}. Also supports batch {items:[[prompt,count]]} total â‰¤8. Capacity gate: 503 if no claim-eligible accounts (last_used â‰¥24h). Rate limit 30/min, concurrency 3, max 8 images/req.",
        "operationId": "generateImages",
        "security": [{ "x402Payment": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "prompt": {
                    "type": "string",
                    "description": "Text prompt for image generation (3-2000 chars, no control chars)",
                    "minLength": 3,
                    "maxLength": 2000,
                    "example": "a red cube on a blue table"
                  },
                  "count": {
                    "type": "integer",
                    "description": "Images for this prompt (shape A)",
                    "minimum": 1,
                    "maximum": 8,
                    "default": 1,
                    "example": 1
                  },
                  "items": {
                    "type": "array",
                    "description": "Batch shape B: [[prompt, count]] total â‰¤8. Use instead of prompt+count for multi-prompt requests",
                    "items": {
                      "type": "array",
                      "prefixItems": [
                        { "type": "string", "description": "prompt" },
                        { "type": "integer", "minimum": 1, "maximum": 8, "description": "count" }
                      ]
                    },
                    "example": [["a red cube", 1], ["a blue sphere", 2]]
                  },
                  "quality": {
                    "type": "string",
                    "enum": ["low"],
                    "description": "Quality tier â€” only low served (others 400)",
                    "default": "low",
                    "example": "low"
                  },
                  "megapixels": {
                    "type": "string",
                    "enum": ["1K"],
                    "description": "Resolution â€” only 1K served (others 400)",
                    "default": "1K",
                    "example": "1K"
                  },
                  "aspect": {
                    "type": "string",
                    "enum": ["Auto", "9:16", "16:9", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9"],
                    "description": "Aspect ratio (free, all served)",
                    "default": "1:1",
                    "example": "1:1"
                  },
                  "id": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9_-]{1,64}$",
                    "description": "Idempotency key â€” replay returns cached result (no re-charge, no re-generation)",
                    "example": "my-job-123"
                  }
                },
                "required": ["prompt"],
                "anyOf": [
                  { "required": ["prompt"] },
                  { "required": ["items"] }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Paid â€” job queued, poll pollUrl",
            "headers": {
              "PAYMENT-RESPONSE": {
                "description": "Base64 x402 settlement receipt (payer, transaction, network)",
                "schema": { "type": "string" }
              },
              "X-Request-Id": { "schema": { "type": "string" } }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ok", "jobId", "status", "pollUrl"],
                  "properties": {
                    "ok": { "type": "boolean", "example": true },
                    "jobId": { "type": "string", "example": "abc123_msxz" },
                    "status": { "type": "string", "enum": ["processing"], "example": "processing" },
                    "pollUrl": { "type": "string", "format": "uri", "example": "https://x402.chaly.org/result/abc123_msxz" },
                    "estimatedSeconds": { "type": "integer", "example": 85 },
                    "charged": {
                      "type": "object",
                      "properties": {
                        "per_image_usd": { "type": "number", "example": 0.005 },
                        "total_usd": { "type": "number", "example": 0.005 },
                        "count": { "type": "integer", "example": 1 },
                        "price_string": { "type": "string", "example": "`$0.005" }
                      }
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment Required â€” pay per PAYMENT-REQUIRED header",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "Base64 JSON x402 offer {x402Version:2, accepts:[{scheme:exact, network:eip155:8453, asset:0x833589..., payTo:0x5340..., amount:5000, maxTimeoutSeconds:900, extra:{name:USD Coin, version:2}}]}",
                "schema": { "type": "string" }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "x402Version": { "type": "integer", "example": 2 },
                    "error": { "type": "string", "example": "Payment required" },
                    "accepts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "scheme": { "type": "string", "example": "exact" },
                          "network": { "type": "string", "example": "eip155:8453" },
                          "amount": { "type": "string", "example": "5000" },
                          "asset": { "type": "string", "example": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
                          "payTo": { "type": "string", "example": "0x53406C32dF6e494aFB078279EA3AD752542E5F0f" },
                          "maxTimeoutSeconds": { "type": "integer", "example": 900 }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid params / JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string", "example": "invalid_params" },
                    "message": { "type": "string", "example": "prompt must be at least 3 characters" }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (30/min) or semaphore full (heavy workload)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string", "example": "rate_limited" },
                    "message": { "type": "string" },
                    "retry_after_seconds": { "type": "integer", "example": 60 }
                  }
                }
              }
            }
          },
          "503": {
            "description": "No claim-eligible accounts (capacity gate)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string", "example": "service_unavailable" },
                    "message": { "type": "string" },
                    "retry_after_seconds": { "type": "integer", "example": 900 }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/result/{id}": {
      "get": {
        "tags": ["jobs"],
        "summary": "Poll job status",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "jobId from 202 pollUrl or idempotency key" }
        ],
        "responses": {
          "200": {
            "description": "processing | done | failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "status": { "type": "string", "enum": ["processing", "done", "failed"] },
                    "jobId": { "type": "string" },
                    "pollUrl": { "type": "string" },
                    "estimatedSeconds": { "type": "integer" },
                    "result": {
                      "type": "object",
                      "properties": {
                        "ok": { "type": "boolean" },
                        "url": { "type": "string", "format": "uri" },
                        "urls": { "type": "array", "items": { "type": "string" } },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "prompt": { "type": "string" },
                              "urls": { "type": "array", "items": { "type": "string" } }
                            }
                          }
                        },
                        "images": { "type": "integer" },
                        "charged": { "type": "object" },
                        "params": { "type": "object" },
                        "jobId": { "type": "string" }
                      }
                    },
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": { "description": "job_not_found" },
          "500": { "description": "failed" }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "tags": ["jobs"],
        "summary": "Alias for /result/:id",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "same as /result/:id" } }
      }
    },
    "/images/{file}": {
      "get": {
        "tags": ["jobs"],
        "summary": "White-label PNG",
        "parameters": [{ "name": "file", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[A-Za-z0-9._-]+\\.png$" } }],
        "responses": {
          "200": { "description": "image/png, Cache 7d", "content": { "image/png": { "schema": { "type": "string", "format": "binary" } } } },
          "404": { "description": "not_found" }
        }
      }
    },
    "/health": {
      "get": {
        "tags": ["health"],
        "summary": "Health + pricing + capacity",
        "responses": {
          "200": {
            "description": "operational",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "status": { "type": "string" },
                    "version": { "type": "string" },
                    "network": { "type": "string", "example": "eip155:8453" },
                    "facilitator": { "type": "string" },
                    "payTo": { "type": "string" },
                    "capacity": { "type": "object" },
                    "pricing": { "type": "object" },
                    "jobs": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "tags": ["health"],
        "summary": "Readiness (503 if no eligible accounts)",
        "responses": {
          "200": { "description": "ready" },
          "503": { "description": "no_eligible_accounts" }
        }
      }
    },
    "/livez": { "get": { "tags": ["health"], "summary": "Liveness", "responses": { "200": { "description": "live" } } } },
    "/metrics": { "get": { "tags": ["health"], "summary": "Prometheus metrics", "responses": { "200": { "description": "text/plain" } } } },
    "/.well-known/x402": {
      "get": {
        "tags": ["x402"],
        "summary": "x402 discovery (Bazaar)",
        "responses": {
          "200": {
            "description": "x402Version 2 resource + accepts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "x402Version": { "type": "integer" },
                    "resource": { "type": "string" },
                    "accepts": { "type": "array" },
                    "extensions": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": { "get": { "tags": ["health"], "summary": "This OpenAPI spec", "responses": { "200": { "description": "openapi 3.1.0" } } } }
  },
  "components": {
    "securitySchemes": {
      "x402Payment": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "x402 EIP-3009",
        "description": "Pay via PAYMENT-SIGNATURE header (base64 EIP-3009). Get offer from 402 PAYMENT-REQUIRED header first. See @x402/fetch wrapFetchWithPayment."
      }
    }
  }
}
