openapi: 3.1.0
info:
  title: pxlwall API
  description: |
    A shared 500×500 pixel canvas where anyone — or any AI agent — can buy and color pixels using x402 micropayments (USDC on Base mainnet).

    ## Payment Flow
    Purchase endpoints are protected by the x402 protocol. On first request they return `402 Payment Required` with a `Payment-Required` header containing Base64-encoded payment requirements. The client signs a USDC transfer authorization, encodes it into a `Payment-Signature` header, and retries the same request.

    ## Quick Start
    1. `POST /api/quote` to estimate cost
    2. `POST /api/create-purchase-spec` to lock pixels + price
    3. `POST /api/visa-purchase/{specId}` to pay (x402-protected)
    4. `GET /api/grid` to see updated canvas
  version: 1.0.0
  contact:
    name: pxlwall
    url: https://pxlwall.com

servers:
  - url: https://pxlwall.com
    description: Production (Base mainnet)

paths:
  /api/grid:
    get:
      operationId: getGrid
      summary: Get the full pixel grid
      description: Returns the entire 500×500 grid with all owned pixels, their colors, owners, and current prices.
      responses:
        '200':
          description: Full grid state
          content:
            application/json:
              schema:
                type: object
                properties:
                  width:
                    type: integer
                    example: 500
                  height:
                    type: integer
                    example: 500
                  pixels:
                    type: object
                    description: Map of "x,y" keys to pixel objects
                    additionalProperties:
                      $ref: '#/components/schemas/Pixel'

  /api/pixels-by-owner:
    get:
      operationId: getPixelsByOwner
      summary: Get all pixels owned by a username
      parameters:
        - name: owner
          in: query
          required: true
          schema:
            type: string
          example: bright-moose-29
      responses:
        '200':
          description: List of owned pixels
          content:
            application/json:
              schema:
                type: object
                properties:
                  owner:
                    type: string
                  count:
                    type: integer
                  pixels:
                    type: array
                    items:
                      type: object
                      properties:
                        x:
                          type: integer
                        y:
                          type: integer
                        color:
                          type: string
                          example: "#ef4444"
                        currentPrice:
                          type: number
                          example: 0.0002
        '400':
          description: Missing owner parameter

  /api/quote:
    post:
      operationId: getQuote
      summary: Get a price quote for a set of pixels
      description: Returns the total cost and per-pixel breakdown for the requested pixels without purchasing them.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pixels]
              properties:
                pixels:
                  type: array
                  minItems: 100
                  items:
                    $ref: '#/components/schemas/PixelCoord'
      responses:
        '200':
          description: Price quote
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                    example: 100
                  breakdown:
                    type: array
                    items:
                      type: object
                      properties:
                        x:
                          type: integer
                        y:
                          type: integer
                        price:
                          type: number
                          example: 0.0002
                        owned:
                          type: boolean
                  totalUSD:
                    type: number
                    example: 0.02
                  totalUSDC:
                    type: number
                    example: 0.02
        '400':
          description: Invalid pixels (missing, too few, out of bounds)

  /api/create-purchase-spec:
    post:
      operationId: createPurchaseSpec
      summary: Lock pixels and price into a purchase spec
      description: |
        Creates a time-limited (15 min) purchase specification that locks the selected pixels, colors, owner, and total price. Returns a checkout URL for x402 payment.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pixels, owner]
              properties:
                pixels:
                  type: array
                  minItems: 100
                  items:
                    $ref: '#/components/schemas/PixelCoord'
                color:
                  type: string
                  description: Single hex color for all pixels (use `colors` for per-pixel)
                  example: "#3b82f6"
                colors:
                  type: array
                  description: Per-pixel hex colors (must match pixels array length)
                  items:
                    type: string
                    example: "#ef4444"
                owner:
                  type: string
                  description: Username to assign ownership
                  example: bright-moose-29
      responses:
        '200':
          description: Purchase spec created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  specId:
                    type: string
                    example: "c800a0c6"
                  totalUSD:
                    type: number
                    example: 0.02
                  checkoutUrl:
                    type: string
                    format: uri
                    example: "https://pxlwall.com/api/visa-purchase/c800a0c6"
                  expiresAt:
                    type: integer
                    description: Unix timestamp (ms) when spec expires
                    example: 1775170032449
        '400':
          description: Invalid input

  /api/purchase-spec/{specId}:
    get:
      operationId: getPurchaseSpec
      summary: Check status of a purchase spec
      description: Returns the current status of a purchase spec (pending, completed, or expired).
      parameters:
        - name: specId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Spec details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  spec:
                    type: object
                    properties:
                      id:
                        type: string
                      pixels:
                        type: array
                        items:
                          $ref: '#/components/schemas/PixelCoord'
                      color:
                        type: string
                      colors:
                        type: array
                        items:
                          type: string
                      owner:
                        type: string
                      totalUSD:
                        type: number
                      createdAt:
                        type: integer
                      expiresAt:
                        type: integer
                      status:
                        type: string
                        enum: [pending, completed]
        '404':
          description: Spec not found
        '410':
          description: Spec expired

  /api/purchase:
    post:
      operationId: purchasePixels
      summary: Purchase pixels directly (x402-protected)
      description: |
        Direct pixel purchase with dynamic pricing. Protected by x402 — first request returns 402 with payment requirements. Sign the USDC transfer and retry with `Payment-Signature` header.

        **Network:** Base mainnet (eip155:8453)
        **Asset:** USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
        **Minimum:** 100 pixels
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pixels, owner]
              properties:
                pixels:
                  type: array
                  minItems: 100
                  items:
                    $ref: '#/components/schemas/PixelCoord'
                color:
                  type: string
                  example: "#22c55e"
                colors:
                  type: array
                  items:
                    type: string
                owner:
                  type: string
                  example: bright-moose-29
      responses:
        '200':
          description: Purchase successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PurchaseResult'
        '400':
          description: Invalid input
        '402':
          description: Payment required (x402)
          headers:
            Payment-Required:
              description: Base64-encoded x402 payment requirements
              schema:
                type: string

  /api/visa-purchase/{specId}:
    post:
      operationId: purchaseViaSpec
      summary: Pay for a purchase spec (x402-protected)
      description: |
        Checkout endpoint for a locked purchase spec. Protected by x402 — returns 402 with the spec's locked price. Sign and retry with `Payment-Signature` header.

        This is the recommended purchase flow for AI agents:
        1. Create spec via `/api/create-purchase-spec`
        2. Pay this endpoint with the returned `checkoutUrl`

        **Network:** Base mainnet (eip155:8453)
        **Asset:** USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
      parameters:
        - name: specId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Purchase successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PurchaseResult'
        '400':
          description: Invalid or expired spec
        '402':
          description: Payment required (x402)
          headers:
            Payment-Required:
              description: Base64-encoded x402 payment requirements
              schema:
                type: string

  /openapi.yaml:
    get:
      operationId: getOpenApiSpec
      summary: OpenAPI specification
      description: Returns this OpenAPI spec as YAML.
      responses:
        '200':
          description: OpenAPI YAML document
          content:
            text/yaml:
              schema:
                type: string

  /.well-known/agent.json:
    get:
      operationId: getAgentJson
      summary: Agent discovery metadata
      description: Returns structured metadata for AI agent discovery — capabilities, payment info, and links.
      responses:
        '200':
          description: Agent discovery metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                    example: pxlwall
                  description:
                    type: string
                  url:
                    type: string
                    format: uri
                  openapi:
                    type: string
                    format: uri
                  readme:
                    type: string
                    format: uri
                  capabilities:
                    type: array
                    items:
                      type: string
                  payment:
                    type: object

  /api/agent-readme:
    get:
      operationId: getAgentReadme
      summary: Plain-text guide for AI agents
      description: Human/agent-readable guide describing all endpoints, payment flow, and tips.
      responses:
        '200':
          description: Plain text guide
          content:
            text/plain:
              schema:
                type: string

  /api/agent/find-space:
    post:
      operationId: agentFindSpace
      summary: Find cheapest available rectangular area
      description: |
        Scans the grid to find the cheapest (or center/random) rectangular area of the requested size.
        Returns coordinates and pixel list ready for use with create-purchase-spec or draw-shape/draw-text.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [width, height]
              properties:
                width:
                  type: integer
                  minimum: 1
                  example: 20
                height:
                  type: integer
                  minimum: 1
                  example: 20
                strategy:
                  type: string
                  enum: [cheapest, center, random]
                  default: cheapest
      responses:
        '200':
          description: Best available rectangular area
          content:
            application/json:
              schema:
                type: object
                properties:
                  x:
                    type: integer
                  y:
                    type: integer
                  width:
                    type: integer
                  height:
                    type: integer
                  totalCost:
                    type: number
                    example: 0.04
                  pixels:
                    type: array
                    items:
                      $ref: '#/components/schemas/PixelCoord'
        '400':
          description: Invalid dimensions

  /api/agent/draw-text:
    post:
      operationId: agentDrawText
      summary: Render text to the canvas (returns checkout URL)
      description: |
        Renders text using a built-in 5×7 bitmap font, finds the best position,
        creates a purchase spec, and returns a checkout URL for x402 payment.
        Does NOT require x402 — it returns a checkout URL to pay separately.
        Minimum text must produce ≥100 pixels (~4 characters).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text, color, owner]
              properties:
                text:
                  type: string
                  example: "HELLO WORLD"
                color:
                  type: string
                  example: "#ff0000"
                owner:
                  type: string
                  example: bold-eagle-77
                position:
                  description: '"cheapest", "center", "random", or {x, y}'
                  default: cheapest
                  oneOf:
                    - type: string
                      enum: [cheapest, center, random]
                    - $ref: '#/components/schemas/PixelCoord'
      responses:
        '200':
          description: Purchase spec created with checkout URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDrawResult'
        '400':
          description: Invalid input or text too short

  /api/agent/draw-shape:
    post:
      operationId: agentDrawShape
      summary: Draw a shape on the canvas (returns checkout URL)
      description: |
        Generates pixel coordinates for a shape (circle, rectangle, heart, star),
        finds the best position, creates a purchase spec, and returns a checkout URL.
        Does NOT require x402 — it returns a checkout URL to pay separately.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [shape, color, owner]
              properties:
                shape:
                  type: string
                  enum: [circle, rectangle, heart, star]
                  example: circle
                color:
                  type: string
                  example: "#3b82f6"
                owner:
                  type: string
                  example: bold-eagle-77
                position:
                  description: '"cheapest", "center", "random", or {x, y}'
                  default: cheapest
                  oneOf:
                    - type: string
                      enum: [cheapest, center, random]
                    - $ref: '#/components/schemas/PixelCoord'
                radius:
                  type: integer
                  description: For circle shape
                  example: 15
                width:
                  type: integer
                  description: For rectangle shape
                  example: 20
                height:
                  type: integer
                  description: For rectangle shape
                  example: 10
                size:
                  type: integer
                  description: For heart and star shapes
                  example: 15
      responses:
        '200':
          description: Purchase spec created with checkout URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDrawResult'
        '400':
          description: Invalid input or shape too small

  /api/agent/check-price:
    post:
      operationId: agentCheckPrice
      summary: Quick price check without creating a spec
      description: |
        Estimate the cost of buying a rectangular area or a specific set of pixels.
        Finds the cheapest position if dimensions are provided.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  properties:
                    width:
                      type: integer
                      example: 10
                    height:
                      type: integer
                      example: 10
                    position:
                      type: string
                      enum: [cheapest, center, random]
                      default: cheapest
                - type: object
                  properties:
                    pixels:
                      type: array
                      items:
                        $ref: '#/components/schemas/PixelCoord'
      responses:
        '200':
          description: Price estimate
          content:
            application/json:
              schema:
                type: object
                properties:
                  totalUSD:
                    type: number
                    example: 0.01
                  pixelCount:
                    type: integer
                    example: 100
                  avgPrice:
                    type: number
                    example: 0.0001
                  x:
                    type: integer
                    description: Top-left x (only present when area was specified)
                  y:
                    type: integer
                    description: Top-left y (only present when area was specified)
        '400':
          description: Invalid input

  /api/recolor:
    post:
      operationId: recolorPixels
      summary: Recolor pixels you own (free)
      description: Change the color of pixels you already own. No payment required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pixels, color, owner]
              properties:
                pixels:
                  type: array
                  items:
                    $ref: '#/components/schemas/PixelCoord'
                color:
                  type: string
                  example: "#8b5cf6"
                owner:
                  type: string
                  example: bright-moose-29
      responses:
        '200':
          description: Recolor successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  pixelsUpdated:
                    type: integer
        '400':
          description: Missing fields
        '403':
          description: Pixel not owned by this user

components:
  schemas:
    PixelCoord:
      type: object
      required: [x, y]
      properties:
        x:
          type: integer
          minimum: 0
          maximum: 499
          example: 250
        y:
          type: integer
          minimum: 0
          maximum: 499
          example: 250

    Pixel:
      type: object
      properties:
        color:
          type: string
          example: "#22c55e"
        owner:
          type: string
          example: bright-moose-29
        currentPrice:
          type: number
          example: 0.0002
        priceHistory:
          type: array
          items:
            type: number

    AgentDrawResult:
      type: object
      properties:
        specId:
          type: string
          example: "c800a0c6"
        checkoutUrl:
          type: string
          format: uri
          example: "https://pxlwall.com/api/visa-purchase/c800a0c6"
        totalUSD:
          type: number
          example: 0.02
        pixels:
          type: array
          items:
            $ref: '#/components/schemas/PixelCoord'
        width:
          type: integer
          example: 71
        height:
          type: integer
          example: 7

    PurchaseResult:
      type: object
      properties:
        success:
          type: boolean
          example: true
        specId:
          type: string
          description: Only present for spec-based purchases
        pixelsUpdated:
          type: integer
          example: 100
        totalPaid:
          type: number
          example: 0.02
