{
  "openapi": "3.0.3",
  "info": {
    "title": "Run402 API",
    "version": "2.0.0",
    "description": "Full-stack infrastructure for AI agents. Postgres, REST API, auth, file storage, static site hosting, serverless functions. Pay-per-tier via x402 USDC on Base — subscribe once, then use wallet signatures for all operations within your tier.",
    "contact": {
      "name": "Run402",
      "url": "https://run402.com"
    },
    "guidance": "Run402 is a pay-per-use backend for AI agents: Postgres databases, static hosting, serverless functions, and image generation. All payments use x402 USDC micropayments on Base.\n\n## Quick start\n1. Install the CLI: npm install -g run402\n2. Create an allowance: run402 init\n3. Subscribe to prototype tier (FREE, testnet USDC): run402 tier set prototype\n4. Provision a project: run402 projects provision --name \"my-app\"\n5. Deploy: run402 deploy --manifest app.json\n\n## Payment model\n- Tier subscriptions (prototype $0.10 testnet, hobby $5, team $20) unlock all platform features.\n- Image generation is $0.03 per call (x402 micropayment).\n- Auth-only endpoints (projects, deploys, messages) require SIWX wallet identity but no payment.\n\n## Auth\n- x402 endpoints: include a payment header (handled automatically by the CLI or x402 client libraries).\n- SIWX endpoints: sign a CAIP-122 message with your wallet.\n- REST API: use the project's anon_key or service_key as the apikey header.\n\n## Docs\n- Full CLI docs: https://run402.com/llms-cli.txt\n- API docs: https://run402.com/llms.txt"
  },
  "servers": [
    {
      "url": "https://api.run402.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Tiers",
      "description": "Wallet-level tier subscription — subscribe, renew, upgrade, and check status"
    },
    {
      "name": "Projects",
      "description": "Provision and manage database projects (wallet auth, free within tier)"
    },
    {
      "name": "Auth",
      "description": "User signup, login, token refresh, and logout"
    },
    {
      "name": "Admin",
      "description": "SQL migrations, RLS policies, usage, and schema introspection"
    },
    {
      "name": "REST",
      "description": "PostgREST proxy — full CRUD on your tables"
    },
    {
      "name": "Storage",
      "description": "Upload, download, delete, sign, and list files"
    },
    {
      "name": "Faucet",
      "description": "Testnet USDC drip for development"
    },
    {
      "name": "Deployments",
      "description": "Static site deployment (wallet auth, free within tier)"
    },
    {
      "name": "Message",
      "description": "Feedback / contact endpoint (wallet auth, free within tier)"
    },
    {
      "name": "Subdomains",
      "description": "Custom subdomain management for deployments"
    },
    {
      "name": "Functions",
      "description": "Serverless function deploy, invoke, logs, and secrets"
    },
    {
      "name": "Health",
      "description": "Health check and x402 discovery"
    },
    {
      "name": "Billing",
      "description": "Prepaid allowance accounts and checkout"
    },
    {
      "name": "Bundle",
      "description": "One-call full-stack app deployment (wallet auth, free within tier)"
    },
    {
      "name": "Publish",
      "description": "Publish and fork app versions"
    },
    {
      "name": "Image",
      "description": "AI image generation (x402-gated, per-call)"
    },
    {
      "name": "Agent",
      "description": "Agent contact registration (wallet auth, free within tier)"
    },
    {
      "name": "Mailboxes",
      "description": "Project-scoped email at slug@mail.run402.com"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Health check",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Service health status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "healthy"
                    },
                    "checks": {
                      "type": "object",
                      "properties": {
                        "postgres": {
                          "type": "string"
                        },
                        "postgrest": {
                          "type": "string"
                        }
                      }
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/ping/v1": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Authenticated ping (wallet auth)",
        "operationId": "ping",
        "description": "Health probe that verifies wallet auth is working. Free within tier.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Ping accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "paid": {
                      "type": "boolean",
                      "example": true
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/.well-known/x402": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "x402 resource discovery",
        "operationId": "x402Discovery",
        "description": "Lists all x402-gated resources and their prices for automated discovery.",
        "responses": {
          "200": {
            "description": "x402 resource list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["version", "resources", "mppResources"],
                  "properties": {
                    "version": {
                      "type": "integer",
                      "example": 1
                    },
                    "description": {
                      "type": "string"
                    },
                    "instructions": {
                      "type": "string"
                    },
                    "resources": {
                      "type": "array",
                      "description": "x402-gated resource URIs",
                      "items": {
                        "type": "string",
                        "format": "uri"
                      }
                    },
                    "mppResources": {
                      "type": "array",
                      "description": "MPP-gated resource URIs (currently same set as resources)",
                      "items": {
                        "type": "string",
                        "format": "uri"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tiers/v1": {
      "get": {
        "tags": [
          "Tiers"
        ],
        "summary": "List tier pricing",
        "operationId": "listTiers",
        "description": "Returns available tiers, pricing, and resource limits. No auth required. Useful for programmatic price discovery before subscribing.",
        "responses": {
          "200": {
            "description": "Tier pricing and auth info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tiers": {
                      "$ref": "#/components/schemas/TierPricing"
                    },
                    "auth": {
                      "type": "object",
                      "properties": {
                        "method": {
                          "type": "string",
                          "example": "EIP-4361 wallet signature"
                        },
                        "headers": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "example": ["X-Run402-Wallet", "X-Run402-Signature", "X-Run402-Timestamp"]
                        },
                        "message_format": {
                          "type": "string",
                          "example": "run402:{unix_timestamp}"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tiers/v1/{tier}": {
      "post": {
        "tags": [
          "Tiers"
        ],
        "summary": "Subscribe, renew, or upgrade tier",
        "operationId": "setTier",
        "description": "Unified tier endpoint. Auto-detects the action: subscribe (no tier or expired), renew (same tier, active), or upgrade (higher tier). Response includes an `action` field indicating what happened. Downgrading during an active lease is rejected.",
        "x-payment-info": {
          "protocols": ["x402"],
          "pricingMode": "range",
          "minPrice": "$0.10",
          "maxPrice": "$20.00"
        },
        "security": [
          {
            "x402": []
          }
        ],
        "parameters": [
          {
            "name": "tier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "prototype",
                "hobby",
                "team"
              ]
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Tier subscribed (new subscription)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TierSubscription"
                }
              }
            }
          },
          "200": {
            "description": "Tier renewed or upgraded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TierSubscription"
                }
              }
            }
          },
          "400": {
            "description": "Cannot downgrade during active lease"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    },
    "/tiers/v1/status": {
      "get": {
        "tags": [
          "Tiers"
        ],
        "summary": "Get wallet tier status",
        "operationId": "getTierStatus",
        "description": "Returns the wallet's current tier, lease expiry, and resource pool usage. Requires wallet auth.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Wallet tier status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["wallet", "tier", "lease_started_at", "lease_expires_at", "active", "pool_usage"],
                  "properties": {
                    "wallet": {
                      "type": "string",
                      "description": "Lowercased wallet address."
                    },
                    "tier": {
                      "type": "string",
                      "nullable": true,
                      "enum": ["prototype", "hobby", "team", null],
                      "description": "Current tier, or null if the wallet has never subscribed."
                    },
                    "lease_started_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "ISO-8601 timestamp when the current lease began. Null if no tier."
                    },
                    "lease_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "ISO-8601 timestamp when the current lease expires. Null if no tier."
                    },
                    "active": {
                      "type": "boolean",
                      "description": "True when tier is set and lease_expires_at is in the future."
                    },
                    "pool_usage": {
                      "type": "object",
                      "required": ["projects", "total_api_calls", "total_storage_bytes", "api_calls_limit", "storage_bytes_limit"],
                      "description": "Aggregate usage across all active projects owned by this wallet.",
                      "properties": {
                        "projects": {
                          "type": "integer",
                          "description": "Number of active projects owned by the wallet."
                        },
                        "total_api_calls": {
                          "type": "integer",
                          "description": "Sum of api_calls across the wallet's active projects."
                        },
                        "total_storage_bytes": {
                          "type": "integer",
                          "description": "Sum of storage_bytes across the wallet's active projects."
                        },
                        "api_calls_limit": {
                          "type": "integer",
                          "description": "Tier API-call cap. 0 when no tier is set."
                        },
                        "storage_bytes_limit": {
                          "type": "integer",
                          "description": "Tier storage cap in bytes. 0 when no tier is set."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "operationId": "listProjects",
        "description": "List projects. Wallet auth sees own projects; admin auth (ADMIN_KEY or admin wallet SIWx) sees all projects. Supports cursor-based pagination.",
        "security": [
          {
            "walletAuth": []
          },
          {
            "adminKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            },
            "description": "Maximum number of projects to return (default 50, max 200)"
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor for pagination (from previous response's next_cursor)"
          }
        ],
        "responses": {
          "200": {
            "description": "Project list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectCreated"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Provision a new project",
        "operationId": "createProject",
        "description": "Creates a new database project. Requires wallet auth (EIP-4361 signature). Free within your active tier. The wallet must have an active tier subscription.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Optional project name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectCreated"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/quote": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Quote tier pricing",
        "operationId": "quoteTiers",
        "description": "Same as GET /projects/v1 — returns tier pricing info.",
        "responses": {
          "200": {
            "description": "Tier pricing info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tiers": {
                      "$ref": "#/components/schemas/TierPricing"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/v1/{id}": {
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Archive (soft-delete) a project",
        "operationId": "deleteProject",
        "description": "Archives and soft-deletes a project. Accepts service_key or admin auth (ADMIN_KEY or admin wallet SIWx).",
        "security": [
          {
            "serviceKey": []
          },
          {
            "adminKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Project purged (terminal delete)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "purged"
                    },
                    "project_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/v1/signup": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Create a new user",
        "operationId": "signup",
        "security": [
          {
            "apikey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 6
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/v1/magic-link": {
      "post": {
        "tags": ["Auth"],
        "summary": "Request a magic link email",
        "description": "Sends a one-time login link to the given email. If the email doesn't exist, a user is auto-created on token verification. Response is always 200 regardless of whether the email exists (no account enumeration).",
        "security": [{"apikey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "redirect_url"],
                "properties": {
                  "email": {"type": "string", "format": "email"},
                  "redirect_url": {"type": "string", "format": "uri", "description": "URL to redirect to with ?token=<token>. Must be an allowed origin for this project."}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Magic link sent (or silently skipped if email invalid)"},
          "400": {"description": "Missing email or redirect_url, or redirect_url not allowed"},
          "429": {"description": "Rate limited (per-email or per-project)"}
        }
      }
    },
    "/auth/v1/user/password": {
      "put": {
        "tags": ["Auth"],
        "summary": "Change, reset, or set password",
        "description": "Three modes: (1) Change: provide current_password + new_password. (2) Reset via magic link: login with magic link, call with just new_password. (3) Set for passwordless user: requires project allow_password_set=true. Requires BOTH the project apikey header AND the user access_token as Authorization: Bearer.",
        "security": [{"apikey": [], "bearerAuth": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["new_password"],
                "properties": {
                  "current_password": {"type": "string", "description": "Required for password change (not for reset/set)"},
                  "new_password": {"type": "string"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Password updated"},
          "401": {"description": "Wrong current_password or invalid token"},
          "403": {"description": "Password set not enabled for this project (passwordless user)"}
        }
      }
    },
    "/auth/v1/settings": {
      "patch": {
        "tags": ["Auth"],
        "summary": "Update project auth settings",
        "description": "Toggle auth settings like allow_password_set. Requires service_key.",
        "security": [{"apikey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "allow_password_set": {"type": "boolean", "description": "Allow passwordless users to set a password (default: false)"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Settings updated"},
          "403": {"description": "Service key required"}
        }
      }
    },
    "/auth/v1/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Login or refresh token",
        "operationId": "getToken",
        "security": [
          {
            "apikey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "email",
                      "password"
                    ],
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "password": {
                        "type": "string"
                      }
                    },
                    "title": "Password login"
                  },
                  {
                    "type": "object",
                    "required": [
                      "refresh_token",
                      "grant_type"
                    ],
                    "properties": {
                      "refresh_token": {
                        "type": "string"
                      },
                      "grant_type": {
                        "type": "string",
                        "enum": [
                          "refresh_token"
                        ]
                      }
                    },
                    "title": "Token refresh"
                  },
                  {
                    "type": "object",
                    "required": [
                      "grant_type",
                      "token"
                    ],
                    "properties": {
                      "grant_type": {
                        "type": "string",
                        "enum": [
                          "magic_link"
                        ]
                      },
                      "token": {
                        "type": "string",
                        "description": "Single-use token delivered to the user's email inbox via POST /auth/v1/magiclink."
                      }
                    },
                    "title": "Magic link login"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "example": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/v1/user": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Get current user",
        "description": "Returns the current user's profile (includes linked identities). Requires BOTH the project apikey header AND the user access_token as Authorization: Bearer.",
        "operationId": "getUser",
        "security": [
          {
            "apikey": [],
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current user info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/v1/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Logout / invalidate refresh token",
        "description": "Invalidates the refresh_token. Requires BOTH the project apikey header AND the user access_token as Authorization: Bearer.",
        "operationId": "logout",
        "security": [
          {
            "apikey": [],
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "refresh_token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/v1/providers": {
      "get": {
        "tags": ["Auth"],
        "summary": "List available auth providers",
        "operationId": "getAuthProviders",
        "description": "Returns available authentication providers for the project (password + OAuth).",
        "security": [{ "apikey": [] }],
        "responses": {
          "200": {
            "description": "Provider list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "password": {
                      "type": "object",
                      "properties": {
                        "enabled": { "type": "boolean", "example": true }
                      }
                    },
                    "oauth": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "provider": { "type": "string", "example": "google" },
                          "enabled": { "type": "boolean", "example": true },
                          "display_name": { "type": "string", "example": "Google" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/v1/oauth/google/start": {
      "post": {
        "tags": ["Auth"],
        "summary": "Start Google OAuth flow",
        "operationId": "startGoogleOAuth",
        "description": "Initiates Google OAuth for app end-users. Returns an authorization_url to open in a popup or redirect. After Google auth, the callback delivers a one-time code to your redirect_url, which you exchange via POST /auth/v1/token?grant_type=authorization_code.",
        "security": [{ "apikey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["redirect_url"],
                "properties": {
                  "redirect_url": { "type": "string", "description": "Where to send the auth code after Google login. Must be http://localhost:* or a claimed subdomain (https://{name}.run402.com)." },
                  "mode": { "type": "string", "enum": ["redirect", "popup"], "default": "redirect", "description": "Redirect mode (default) navigates the full page. Popup mode opens a window but window.close() may be blocked by browser COOP policies." },
                  "intent": { "type": "string", "enum": ["signin", "link"], "default": "signin" },
                  "code_challenge": { "type": "string", "description": "PKCE code challenge (base64url of SHA-256 of verifier)" },
                  "code_challenge_method": { "type": "string", "enum": ["S256"], "default": "S256" },
                  "client_state": { "type": "string", "description": "Opaque state passed back to your redirect_url" },
                  "login_hint": { "type": "string", "description": "Email hint for Google account picker" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authorization URL to open in browser",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "provider": { "type": "string", "example": "google" },
                    "authorization_url": { "type": "string", "format": "uri" },
                    "expires_in": { "type": "integer", "example": 600 }
                  }
                }
              }
            }
          },
          "400": { "description": "Bad request" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/projects/v1/admin/{id}/sql": {
      "post": {
        "tags": [
          "Admin"
        ],
        "summary": "Run SQL migration",
        "operationId": "runSql",
        "description": "Executes SQL against the project database. Dangerous statements (CREATE EXTENSION, ALTER SYSTEM, etc.) are blocked.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sql"
                ],
                "properties": {
                  "sql": {
                    "type": "string",
                    "description": "SQL statement(s) to execute"
                  }
                }
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "description": "Raw SQL text"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SQL executed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "schema": {
                      "type": "string"
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "rowCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/rls": {
      "post": {
        "tags": [
          "Admin"
        ],
        "summary": "Apply RLS policy template",
        "operationId": "applyRls",
        "description": "Applies a row-level security template to one or more tables.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "template",
                  "tables"
                ],
                "properties": {
                  "template": {
                    "type": "string",
                    "enum": [
                      "user_owns_rows",
                      "public_read",
                      "public_read_write"
                    ],
                    "description": "RLS template to apply"
                  },
                  "tables": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["table"],
                      "properties": {
                        "table": {
                          "type": "string",
                          "description": "Table name within the project's schema."
                        },
                        "owner_column": {
                          "type": "string",
                          "description": "Column that stores the owning user's UUID. Required when template is 'user_owns_rows'; ignored otherwise."
                        }
                      }
                    },
                    "description": "Tables to apply the policy to. Each entry is an object: { \"table\": \"notes\", \"owner_column\": \"user_id\" }. owner_column is required when template is 'user_owns_rows'.",
                    "example": [
                      { "table": "notes", "owner_column": "user_id" }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "RLS applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "template": {
                      "type": "string"
                    },
                    "tables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/usage": {
      "get": {
        "tags": [
          "Admin"
        ],
        "summary": "Get project usage",
        "operationId": "getUsage",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "tier": {
                      "type": "string"
                    },
                    "api_calls": {
                      "type": "integer"
                    },
                    "api_calls_limit": {
                      "type": "integer"
                    },
                    "storage_bytes": {
                      "type": "integer"
                    },
                    "storage_limit_bytes": {
                      "type": "integer"
                    },
                    "lease_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "status": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/schema": {
      "get": {
        "tags": [
          "Admin"
        ],
        "summary": "Introspect project schema",
        "operationId": "getSchema",
        "description": "Returns full schema introspection: tables, columns, constraints, RLS policies.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Schema info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schema": {
                      "type": "string"
                    },
                    "tables": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "columns": {
                            "type": "array",
                            "items": {
                              "type": "object"
                            }
                          },
                          "constraints": {
                            "type": "array",
                            "items": {
                              "type": "object"
                            }
                          },
                          "rls_enabled": {
                            "type": "boolean"
                          },
                          "policies": {
                            "type": "array",
                            "items": {
                              "type": "object"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rest/v1/{path}": {
      "get": {
        "tags": [
          "REST"
        ],
        "summary": "PostgREST proxy — read data",
        "operationId": "restGet",
        "description": "Proxies GET requests to PostgREST. Supports all PostgREST query parameters (select, order, limit, offset, filters). The apikey header determines the role (anon or authenticated user).",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Table name or RPC function"
          },
          {
            "name": "select",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Column selection"
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Sort order"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Row limit"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Row offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Query results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "REST"
        ],
        "summary": "PostgREST proxy — insert data",
        "operationId": "restPost",
        "description": "Proxies POST requests to PostgREST for inserting rows.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object"
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Row(s) inserted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "REST"
        ],
        "summary": "PostgREST proxy — update data",
        "operationId": "restPatch",
        "description": "Proxies PATCH requests to PostgREST for updating rows. Use query parameters to filter which rows to update.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Row(s) updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "REST"
        ],
        "summary": "PostgREST proxy — delete data",
        "operationId": "restDelete",
        "description": "Proxies DELETE requests to PostgREST. Use query parameters to filter which rows to delete.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Row(s) deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/storage/v1/object/{bucket}/{path}": {
      "post": {
        "tags": [
          "Storage"
        ],
        "summary": "Upload a file (deprecated)",
        "operationId": "uploadFile",
        "deprecated": true,
        "description": "DEPRECATED — sunset 2026-06-01. Use POST /storage/v1/uploads instead (supports up to 5 TiB, direct-to-S3). Legacy upload capped at 50 MB; returns 413 with migration hint above that.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "File path within the bucket"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "text/plain": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Storage"
        ],
        "summary": "Download a file",
        "operationId": "downloadFile",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "File not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Storage"
        ],
        "summary": "Delete a file",
        "operationId": "deleteFile",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    },
                    "key": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/storage/v1/object/sign/{bucket}/{path}": {
      "post": {
        "tags": [
          "Storage"
        ],
        "summary": "Generate a signed URL (deprecated)",
        "operationId": "signFile",
        "deprecated": true,
        "description": "DEPRECATED — sunset 2026-06-01. Use POST /storage/v1/blob/{key}/sign instead. Generates a pre-signed URL for direct file access. Expires in 1 hour.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signed_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/storage/v1/object/list/{bucket}": {
      "get": {
        "tags": [
          "Storage"
        ],
        "summary": "List files in a bucket (deprecated)",
        "operationId": "listFiles",
        "deprecated": true,
        "description": "DEPRECATED — sunset 2026-06-01. Use GET /storage/v1/blobs?prefix=<bucket>/ instead.",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File listing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "objects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer"
                          },
                          "last_modified": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/storage/v1/uploads": {
      "post": {
        "tags": ["Storage"],
        "summary": "Create upload session",
        "operationId": "createUploadSession",
        "description": "Create a direct-to-S3 upload session. The gateway returns presigned URLs the client PUTs bytes to; the gateway never buffers the upload body itself. Works for any size from 1 byte up to 5 TiB.",
        "security": [{"apikey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["key", "size_bytes"],
                "properties": {
                  "key": {"type": "string", "description": "Destination path within the project's blob namespace"},
                  "size_bytes": {"type": "integer", "format": "int64", "minimum": 1, "maximum": 5497558138880},
                  "content_type": {"type": "string"},
                  "sha256": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"},
                  "visibility": {"type": "string", "enum": ["public", "private"], "default": "public"},
                  "immutable": {"type": "boolean", "default": false, "description": "When true, adds a content-hash suffix to the URL so overwrites produce distinct URLs. Requires sha256."},
                  "ttl_seconds": {"type": "integer", "minimum": 60, "maximum": 604800}
                }
              }
            }
          }
        },
        "responses": {
          "201": {"description": "Upload session created"},
          "400": {"description": "Invalid input (bad key, bad size, immutable without sha256, …)"},
          "402": {"description": "Would exceed storage quota"},
          "413": {"description": "size_bytes exceeds 5 TiB"}
        }
      }
    },
    "/storage/v1/uploads/{id}": {
      "get": {
        "tags": ["Storage"],
        "summary": "Upload session status",
        "operationId": "getUploadSession",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "Upload session state"},
          "404": {"description": "Not found (or owned by another project)"}
        }
      },
      "delete": {
        "tags": ["Storage"],
        "summary": "Abort upload session",
        "operationId": "abortUploadSession",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "Session aborted"},
          "404": {"description": "Not found"}
        }
      }
    },
    "/storage/v1/uploads/{id}/complete": {
      "post": {
        "tags": ["Storage"],
        "summary": "Complete an upload session",
        "operationId": "completeUploadSession",
        "description": "Finalize an upload. For multipart sessions, the client supplies the parts list with their ETags from S3. The gateway verifies the SHA-256 (if declared), settles storage_bytes, and returns the public URL.",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "id", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["part_number", "etag"],
                      "properties": {
                        "part_number": {"type": "integer", "minimum": 1, "maximum": 10000},
                        "etag": {"type": "string"},
                        "sha256": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"}
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Upload completed; returns { key, size_bytes, sha256, visibility, url, immutable_url }"},
          "402": {"description": "Actual size overflows the storage quota"},
          "404": {"description": "Upload session not found"},
          "409": {"description": "SHA-256 mismatch, size disagrees with declaration, or session already completed/aborted"}
        }
      }
    },
    "/storage/v1/blob/{key}": {
      "get": {
        "tags": ["Storage"],
        "summary": "Download blob",
        "operationId": "getBlob",
        "description": "Stream a stored blob. Public blobs may be fetched anonymously (supply ?project_id= for gateway-direct access; the CDN subdomain URL requires no query). Private blobs require apikey from the owning project. Supports HTTP Range requests.",
        "security": [{"apikey": []}, {}],
        "parameters": [
          {"name": "key", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "project_id", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Required when no apikey header is present (public blob access via gateway)"}
        ],
        "responses": {
          "200": {"description": "Blob bytes", "content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}},
          "206": {"description": "Partial content (byte-range)"},
          "401": {"description": "Authentication required"},
          "404": {"description": "Blob not found or private-without-auth"}
        }
      },
      "delete": {
        "tags": ["Storage"],
        "summary": "Delete blob",
        "operationId": "deleteBlob",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "key", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "Blob deleted"},
          "404": {"description": "Blob not found"}
        }
      }
    },
    "/storage/v1/blob/{key}/sign": {
      "post": {
        "tags": ["Storage"],
        "summary": "Create time-boxed signed GET URL",
        "operationId": "signBlobUrl",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "key", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ttl_seconds": {"type": "integer", "minimum": 60, "maximum": 604800, "default": 3600}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "{ signed_url, expires_at, expires_in }"},
          "400": {"description": "ttl_seconds out of range"},
          "404": {"description": "Blob not found"}
        }
      }
    },
    "/storage/v1/blobs": {
      "get": {
        "tags": ["Storage"],
        "summary": "List blobs",
        "operationId": "listBlobs",
        "security": [{"apikey": []}],
        "parameters": [
          {"name": "prefix", "in": "query", "required": false, "schema": {"type": "string"}},
          {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100}},
          {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "{ blobs: [...], next_cursor }"}
        }
      }
    },
    "/storage/v1/public/{project_id}/{bucket}/{path}": {
      "get": {
        "tags": [
          "Storage"
        ],
        "summary": "Public file access (deprecated)",
        "operationId": "publicFileAccess",
        "deprecated": true,
        "description": "DEPRECATED — sunset 2026-06-01. Replaced by CDN subdomain URLs (https://<sub>.run402.com/_blob/<key>) returned from upload-complete. Publicly accessible file download. No authentication required.",
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project ID"
          },
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Storage bucket name"
          },
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "File path within the bucket"
          }
        ],
        "responses": {
          "200": {
            "description": "File content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Project not found, project inactive, or file not found"
          }
        }
      }
    },
    "/faucet/v1": {
      "post": {
        "tags": [
          "Faucet"
        ],
        "summary": "Request testnet USDC",
        "operationId": "faucetDrip",
        "description": "Sends testnet USDC to the specified wallet address on Base Sepolia. Rate limited to 1 drip per IP per 24 hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "Ethereum wallet address (0x...)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "USDC sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transaction_hash": {
                      "type": "string"
                    },
                    "amount_usd_micros": {
                      "type": "integer"
                    },
                    "token": {
                      "type": "string",
                      "example": "USDC"
                    },
                    "network": {
                      "type": "string",
                      "example": "base-sepolia"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited — 1 drip per IP per 24 hours"
          }
        }
      }
    },
    "/faucet/v1/admin": {
      "post": {
        "tags": [
          "Faucet"
        ],
        "summary": "Admin faucet drip (no rate limit)",
        "operationId": "adminFaucetDrip",
        "description": "Admin-only faucet with no rate limit and optional custom amount. Requires x-admin-key.",
        "security": [
          {
            "serviceKey": [],
            "adminKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "string",
                    "description": "Custom USDC amount"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "USDC sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transaction_hash": {
                      "type": "string"
                    },
                    "amount_usd_micros": {
                      "type": "integer"
                    },
                    "token": {
                      "type": "string"
                    },
                    "network": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden — invalid admin key"
          }
        }
      }
    },
    "/deployments/v1": {
      "get": {
        "tags": [
          "Deployments"
        ],
        "summary": "Deployment info",
        "operationId": "deploymentsInfo",
        "description": "Returns deployment info. No auth required.",
        "responses": {
          "200": {
            "description": "Deployment info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "description": {
                      "type": "string"
                    },
                    "method": {
                      "type": "string"
                    },
                    "body": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Deployments"
        ],
        "summary": "Deploy a static site",
        "operationId": "createDeployment",
        "description": "Deploys static files to a live URL at {id}.sites.run402.com. Supports HTML, CSS, JS, images. SPA-ready with automatic fallback routing. Max 50MB total. Idempotent with Idempotency-Key header. Requires wallet auth, free within tier.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "files"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Deployment name"
                  },
                  "project": {
                    "type": "string",
                    "description": "Optional project ID to associate with"
                  },
                  "target": {
                    "type": "string",
                    "description": "Optional deploy target"
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "file",
                        "data"
                      ],
                      "properties": {
                        "file": {
                          "type": "string",
                          "description": "File path (e.g. index.html, css/style.css)"
                        },
                        "data": {
                          "type": "string",
                          "description": "File content (UTF-8 text or base64-encoded)"
                        },
                        "encoding": {
                          "type": "string",
                          "enum": [
                            "utf-8",
                            "base64"
                          ],
                          "default": "utf-8"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Site deployed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "status": {
                      "type": "string"
                    },
                    "files_count": {
                      "type": "integer"
                    },
                    "total_size": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/deployments/v1/{id}": {
      "get": {
        "tags": [
          "Deployments"
        ],
        "summary": "Get deployment status",
        "operationId": "getDeployment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deployment details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "status": {
                      "type": "string"
                    },
                    "files_count": {
                      "type": "integer"
                    },
                    "total_size": {
                      "type": "integer"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Deployment not found"
          }
        }
      }
    },
    "/agent/v1/contact": {
      "get": {
        "tags": [
          "Agent"
        ],
        "summary": "Agent contact endpoint info",
        "operationId": "getAgentContactInfo",
        "responses": {
          "200": {
            "description": "Endpoint info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Agent"
        ],
        "summary": "Register agent contact info",
        "operationId": "postAgentContact",
        "description": "Register or update agent contact info (name, email, webhook) tied to the wallet. Requires wallet auth, free within tier.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Agent name (required)"
                  },
                  "email": {
                    "type": "string",
                    "description": "Contact email (optional)"
                  },
                  "webhook": {
                    "type": "string",
                    "description": "Webhook URL, must be https (optional)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact info saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallet": {
                      "type": "string",
                      "example": "0x..."
                    },
                    "name": {
                      "type": "string",
                      "example": "my-agent"
                    },
                    "email": {
                      "type": "string",
                      "example": "ops@example.com"
                    },
                    "webhook": {
                      "type": "string",
                      "example": "https://example.com/hook"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing name, invalid email, non-https webhook)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/message/v1": {
      "get": {
        "tags": [
          "Message"
        ],
        "summary": "Message endpoint info",
        "operationId": "getMessageInfo",
        "responses": {
          "200": {
            "description": "Endpoint info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Message"
        ],
        "summary": "Send a message",
        "operationId": "sendMessage",
        "description": "Sends a message (feedback, contact) to the Run402 team via Telegram. Requires wallet auth, free within tier. Idempotent.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "Message text"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "sent"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/ai/v1/translate": {
      "post": {
        "tags": ["AI"],
        "summary": "Translate text",
        "description": "Translate text to a target language via OpenRouter. Requires an active AI Translation add-on.",
        "security": [{"ServiceKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["text", "to"],
                "properties": {
                  "text": {"type": "string", "description": "Text to translate (max 10,000 chars)"},
                  "to": {"type": "string", "description": "Target language (ISO 639-1 code)"},
                  "from": {"type": "string", "description": "Source language (ISO 639-1 code, auto-detected if omitted)"},
                  "context": {"type": "string", "description": "Context hint for tone/register (max 200 chars)"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Translation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "text": {"type": "string"},
                    "from": {"type": "string"},
                    "to": {"type": "string"}
                  }
                }
              }
            }
          },
          "400": {"description": "Invalid input"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "402": {"description": "Translation add-on required or word limit reached"},
          "429": {"description": "Rate limit exceeded"}
        }
      }
    },
    "/ai/v1/moderate": {
      "post": {
        "tags": ["AI"],
        "summary": "Moderate content",
        "description": "Check content against OpenAI moderation categories. Free for all projects.",
        "security": [{"ServiceKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["text"],
                "properties": {
                  "text": {"type": "string", "description": "Text to moderate (max 10,000 chars)"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moderation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "flagged": {"type": "boolean"},
                    "categories": {"type": "object"},
                    "category_scores": {"type": "object"}
                  }
                }
              }
            }
          },
          "400": {"description": "Invalid input"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "429": {"description": "Rate limit exceeded"}
        }
      }
    },
    "/ai/v1/usage": {
      "get": {
        "tags": ["AI"],
        "summary": "Translation usage",
        "description": "Get translation usage for the current billing period.",
        "security": [{"ServiceKey": []}],
        "responses": {
          "200": {
            "description": "Usage data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "translation": {
                      "type": "object",
                      "properties": {
                        "active": {"type": "boolean"},
                        "used_words": {"type": "integer"},
                        "included_words": {"type": "integer"},
                        "remaining_words": {"type": "integer"},
                        "billing_cycle_start": {"type": "string", "format": "date-time", "nullable": true}
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },
    "/ai/v1/addons": {
      "post": {
        "tags": ["AI"],
        "summary": "Activate translation add-on",
        "operationId": "activateAddon",
        "description": "Activate a translation add-on for a project. Admin only.",
        "security": [{"adminKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["project_id", "addon_type", "included_words"],
                "properties": {
                  "project_id": {"type": "string", "description": "Project ID to activate the add-on for"},
                  "addon_type": {"type": "string", "enum": ["translation"], "description": "Add-on type (currently only 'translation')"},
                  "included_words": {"type": "integer", "description": "Number of words included in the add-on package"}
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Add-on activated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project_id": {"type": "string"},
                    "addon_type": {"type": "string"},
                    "included_words": {"type": "integer"},
                    "included_tokens": {"type": "integer"},
                    "status": {"type": "string", "enum": ["active"]}
                  }
                }
              }
            }
          },
          "400": {"description": "Invalid input (missing project_id, invalid addon_type, or invalid included_words)"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      },
      "delete": {
        "tags": ["AI"],
        "summary": "Deactivate translation add-on",
        "operationId": "deactivateAddon",
        "description": "Deactivate a translation add-on for a project. Admin only.",
        "security": [{"adminKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["project_id", "addon_type"],
                "properties": {
                  "project_id": {"type": "string", "description": "Project ID to deactivate the add-on for"},
                  "addon_type": {"type": "string", "enum": ["translation"], "description": "Add-on type (currently only 'translation')"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Add-on deactivated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {"type": "string", "enum": ["deactivated"]},
                    "project_id": {"type": "string"},
                    "addon_type": {"type": "string"}
                  }
                }
              }
            }
          },
          "400": {"description": "Invalid input (missing project_id or invalid addon_type)"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"description": "No active add-on found for this project"}
        }
      }
    },
    "/generate-image/v1": {
      "get": {
        "tags": [
          "Image"
        ],
        "summary": "Image generation info",
        "operationId": "getGenerateImageInfo",
        "responses": {
          "200": {
            "description": "Image generation pricing and options",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Image"
        ],
        "summary": "Generate an image",
        "operationId": "generateImage",
        "description": "Generates an image via AI. Requires x402 payment. Not included in any tier — per-call cost.",
        "x-payment-info": {
          "protocols": ["x402"],
          "pricingMode": "fixed",
          "price": "$0.03"
        },
        "security": [
          {
            "x402": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "description": "Image generation prompt"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Image generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    },
    "/wallets/v1/{address}/projects": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "List projects by wallet",
        "operationId": "listWalletProjects",
        "description": "Lists all projects associated with a wallet address.",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ethereum wallet address"
          }
        ],
        "responses": {
          "200": {
            "description": "Wallet projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallet": {
                      "type": "string"
                    },
                    "projects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "tier": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "api_calls": {
                            "type": "integer"
                          },
                          "storage_bytes": {
                            "type": "integer"
                          },
                          "lease_expires_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/subdomains/v1": {
      "post": {
        "tags": [
          "Subdomains"
        ],
        "summary": "Claim or reassign a subdomain",
        "operationId": "createSubdomain",
        "description": "Maps a custom subdomain ({name}.run402.com) to a deployment.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "deployment_id"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Subdomain name (e.g. 'myapp' for myapp.run402.com)"
                  },
                  "deployment_id": {
                    "type": "string",
                    "description": "Deployment ID to point to"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subdomain created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subdomain"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Subdomains"
        ],
        "summary": "List subdomains",
        "operationId": "listSubdomains",
        "description": "List subdomains. service_key sees the project's subdomains; admin auth (ADMIN_KEY or admin wallet SIWx) sees all subdomains.",
        "security": [
          {
            "serviceKey": []
          },
          {
            "adminKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Subdomain list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subdomains": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subdomain"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/subdomains/v1/{name}": {
      "get": {
        "tags": [
          "Subdomains"
        ],
        "summary": "Look up a subdomain",
        "operationId": "getSubdomain",
        "description": "Public lookup — no auth required.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subdomain details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subdomain"
                }
              }
            }
          },
          "404": {
            "description": "Subdomain not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Subdomains"
        ],
        "summary": "Release a subdomain",
        "operationId": "deleteSubdomain",
        "description": "Releases a subdomain. Accepts service_key or admin auth (ADMIN_KEY or admin wallet SIWx).",
        "security": [
          {
            "serviceKey": []
          },
          {
            "adminKey": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subdomain released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/domains/v1": {
      "post": {
        "tags": ["Custom Domains"],
        "summary": "Register a custom domain",
        "operationId": "registerDomain",
        "description": "Register a custom domain for an existing Run402 subdomain. Returns DNS instructions for verification.",
        "security": [{ "serviceKey": [] }, { "adminKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain", "subdomain_name"],
                "properties": {
                  "domain": { "type": "string", "example": "example.com" },
                  "subdomain_name": { "type": "string", "example": "myapp" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Domain registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": { "type": "string" },
                    "subdomain_name": { "type": "string" },
                    "url": { "type": "string" },
                    "subdomain_url": { "type": "string" },
                    "status": { "type": "string", "example": "pending" },
                    "dns_instructions": { "type": "object" },
                    "project_id": { "type": "string", "nullable": true },
                    "created_at": { "type": "string" },
                    "updated_at": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Bad request" },
          "409": { "description": "Domain already registered" }
        }
      },
      "get": {
        "tags": ["Custom Domains"],
        "summary": "List custom domains",
        "operationId": "listDomains",
        "description": "List custom domains. service_key sees project's domains; admin sees all.",
        "security": [{ "serviceKey": [] }, { "adminKey": [] }],
        "responses": {
          "200": {
            "description": "List of domains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domains": { "type": "array", "items": { "type": "object" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/domains/v1/{domain}": {
      "get": {
        "tags": ["Custom Domains"],
        "summary": "Check domain status",
        "operationId": "getDomain",
        "description": "Check verification and SSL status of a registered domain. Requires service_key or admin auth.",
        "security": [{ "serviceKey": [] }, { "adminKey": [] }],
        "parameters": [{ "name": "domain", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Domain details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": { "type": "string" },
                    "subdomain_name": { "type": "string" },
                    "status": { "type": "string" },
                    "dns_instructions": { "type": "object" }
                  }
                }
              }
            }
          },
          "401": { "description": "Unauthorized" },
          "404": { "description": "Domain not found" }
        }
      },
      "delete": {
        "tags": ["Custom Domains"],
        "summary": "Release a custom domain",
        "operationId": "deleteDomain",
        "description": "Release a custom domain. Accepts service_key or admin auth.",
        "security": [{ "serviceKey": [] }, { "adminKey": [] }],
        "parameters": [{ "name": "domain", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Domain released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "deleted" },
                    "domain": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": { "description": "Domain not found" }
        }
      }
    },
    "/projects/v1/admin/{id}/functions": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Deploy a serverless function",
        "operationId": "deployFunction",
        "description": "Deploys a JavaScript/TypeScript function to AWS Lambda. Returns an invocation URL.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "code"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Function name"
                  },
                  "code": {
                    "type": "string",
                    "description": "Function source code"
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "timeout": {
                        "type": "integer",
                        "description": "Timeout in seconds"
                      },
                      "memory": {
                        "type": "integer",
                        "description": "Memory in MB"
                      }
                    }
                  },
                  "deps": {
                    "type": "object",
                    "description": "NPM dependencies (name → version)",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Function deployed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "status": {
                      "type": "string"
                    },
                    "runtime": {
                      "type": "string"
                    },
                    "timeout": {
                      "type": "integer"
                    },
                    "memory": {
                      "type": "integer"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "List project functions",
        "operationId": "listFunctions",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Function list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "functions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "runtime": {
                            "type": "string"
                          },
                          "timeout": {
                            "type": "integer"
                          },
                          "memory": {
                            "type": "integer"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/functions/{name}": {
      "patch": {
        "tags": [
          "Functions"
        ],
        "summary": "Update function metadata (schedule, timeout, memory) without redeploying code",
        "operationId": "patchFunction",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "schedule": {
                    "type": "string",
                    "nullable": true,
                    "description": "Cron expression (5-field) or null to remove"
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "timeout": {
                        "type": "integer",
                        "description": "Timeout in seconds"
                      },
                      "memory": {
                        "type": "integer",
                        "description": "Memory in MB"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated function metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "runtime": { "type": "string" },
                    "timeout": { "type": "integer" },
                    "memory": { "type": "integer" },
                    "schedule": { "type": "string", "nullable": true },
                    "updated_at": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Function not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Functions"
        ],
        "summary": "Delete a function",
        "operationId": "deleteFunction",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Function deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/functions/{name}/trigger": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Manually trigger a function",
        "description": "Invoke a function immediately, same as a cron tick. Returns the function's response status and body. Updates schedule_meta if the function has a schedule.",
        "operationId": "triggerFunction",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Function invocation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer",
                      "description": "HTTP status code from the function"
                    },
                    "body": {
                      "type": "string",
                      "description": "Response body from the function"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Function not found"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/functions/{name}/logs": {
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "Get function execution logs",
        "operationId": "getFunctionLogs",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tail",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            },
            "description": "Number of log lines to return"
          }
        ],
        "responses": {
          "200": {
            "description": "Function logs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "logs": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/secrets": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Set a function secret",
        "operationId": "setSecret",
        "description": "Sets an environment variable available to all functions in the project.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key",
                  "value"
                ],
                "properties": {
                  "key": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Secret set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "set"
                    },
                    "key": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "List function secrets",
        "operationId": "listSecrets",
        "description": "Lists secret key names (not values).",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/secrets/{key}": {
      "delete": {
        "tags": [
          "Functions"
        ],
        "summary": "Delete a function secret",
        "operationId": "deleteSecret",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Secret deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/promote-user": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Promote user to project admin",
        "operationId": "promoteUser",
        "description": "Set is_admin = true for a user by email. The user's next JWT will have role: project_admin (BYPASSRLS, can manage secrets).",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User promoted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "promoted"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/demote-user": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Demote project admin to regular user",
        "operationId": "demoteUser",
        "description": "Set is_admin = false for a user by email. The user's next JWT will have role: authenticated (RLS-enforced).",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User demoted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "demoted"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found"
          }
        }
      }
    },
    "/functions/v1": {
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "List functions",
        "operationId": "listAllFunctions",
        "description": "List deployed functions across all projects. Wallet auth sees own functions; admin auth (ADMIN_KEY or admin wallet SIWx) sees all functions.",
        "security": [
          {
            "walletAuth": []
          },
          {
            "adminKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Function list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "functions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/functions/v1/{name}": {
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "Invoke a function (GET)",
        "operationId": "invokeFunctionGet",
        "description": "Invokes a deployed function. Supports any HTTP method. Subroutes are forwarded. Requires the apikey header (anon_key or service_key); a raw request without it returns 401 Missing apikey header. Add Authorization: Bearer <access_token> for authenticated calls so the function can identify the user via getUser(req).",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Function response (varies)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Invoke a function (POST)",
        "operationId": "invokeFunctionPost",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Function response (varies)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Functions"
        ],
        "summary": "Invoke a function (PATCH)",
        "operationId": "invokeFunctionPatch",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Function response (varies)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Functions"
        ],
        "summary": "Invoke a function (DELETE)",
        "operationId": "invokeFunctionDelete",
        "security": [
          {
            "apikey": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Function response (varies)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/billing/v1/accounts/{wallet}": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Get account balance",
        "operationId": "getBillingAccount",
        "parameters": [
          {
            "name": "wallet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ethereum wallet address"
          }
        ],
        "responses": {
          "200": {
            "description": "Account balance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available_usd_micros": {
                      "type": "integer",
                      "description": "Available balance in micro-USD (1 USD = 1,000,000)"
                    }
                  },
                  "required": ["available_usd_micros"]
                }
              }
            }
          }
        }
      }
    },
    "/billing/v1/accounts/{wallet}/history": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Get account transaction history",
        "operationId": "getBillingHistory",
        "parameters": [
          {
            "name": "wallet",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transaction history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallet": {
                      "type": "string"
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/billing/v1/accounts": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create an email-based billing account",
        "description": "Creates a billing account identified by email (Stripe-only, no wallet). Idempotent — duplicate emails return the existing account. Sends a verification email (rate-limited: 60s cooldown, 10/IP/hour, 500/hour + 2000/day global).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": { "email": { "type": "string", "format": "email" } }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Account created" },
          "400": { "description": "Invalid email" },
          "429": { "description": "Verification rate limited" }
        }
      }
    },
    "/billing/v1/accounts/{id}/link-wallet": {
      "post": {
        "tags": ["Billing"],
        "summary": "Link a wallet to an email billing account",
        "description": "Adds a wallet to an existing email account for hybrid Stripe + x402 access. Fails with 409 if the wallet is already linked to another account.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["wallet"],
                "properties": { "wallet": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Wallet linked" },
          "404": { "description": "Account not found" },
          "409": { "description": "Wallet already linked" }
        }
      }
    },
    "/billing/v1/tiers/{tier}/checkout": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create a Stripe tier subscription checkout",
        "description": "Subscribe/renew/upgrade to a run402 tier via credit card (alternative to x402 on-chain payment). Supports both wallet and email identifiers. One-time payment per lease period. On completion, tier is applied automatically (with prorated refund on upgrade).",
        "parameters": [
          { "name": "tier", "in": "path", "required": true, "schema": { "type": "string", "enum": ["prototype", "hobby", "team"] } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "wallet": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
                  "email": { "type": "string", "format": "email" },
                  "success_url": { "type": "string", "format": "uri" },
                  "cancel_url": { "type": "string", "format": "uri" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Checkout session created", "content": { "application/json": { "schema": { "type": "object", "properties": { "checkout_url": { "type": "string" }, "topup_id": { "type": "string" } } } } } },
          "400": { "description": "Invalid tier or missing identifier" }
        }
      }
    },
    "/billing/v1/email-packs/checkout": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create a Stripe email pack checkout ($5 = 10,000 emails)",
        "description": "Buy a prepaid email pack. Each pack is $5 for 10,000 emails. Packs never expire. Require a verified custom sender domain to consume (mail.run402.com is hard-capped at tier limits for spam protection).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "wallet": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$" },
                  "email": { "type": "string", "format": "email" },
                  "success_url": { "type": "string", "format": "uri" },
                  "cancel_url": { "type": "string", "format": "uri" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Checkout session created" },
          "400": { "description": "Missing identifier" }
        }
      }
    },
    "/billing/v1/email-packs/auto-recharge": {
      "post": {
        "tags": ["Billing"],
        "summary": "Enable or disable email pack auto-recharge",
        "description": "When enabled, the platform automatically repurchases a $5 email pack when email_credits_remaining drops below the threshold. Requires a saved Stripe payment method. 3 consecutive failures automatically disable auto-recharge.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["billing_account_id", "enabled"],
                "properties": {
                  "billing_account_id": { "type": "string" },
                  "enabled": { "type": "boolean" },
                  "threshold": { "type": "number", "default": 2000 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Auto-recharge updated" }
        }
      }
    },
    "/billing/v1/checkouts": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Create a top-up checkout",
        "operationId": "createBillingCheckout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "wallet",
                  "amount_usd_micros"
                ],
                "properties": {
                  "wallet": {
                    "type": "string",
                    "description": "Ethereum wallet address"
                  },
                  "amount_usd_micros": {
                    "type": "integer",
                    "description": "Amount in micro-USD"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "checkout_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "topup_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/contracts/v1/wallets": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Provision a KMS-backed Ethereum wallet",
        "description": "Provision a KMS-backed Ethereum wallet for signing smart-contract transactions on behalf of the project. Cost: $0.04/day rental ($1.20/month, billed daily as kms_wallet_rental). Requires $1.20 in cash credit at creation (30 days of rent). Private keys never leave AWS KMS. Supported chains: base-mainnet, base-sepolia. Non-custodial: see https://run402.com/humans/terms.html#non-custodial-kms-wallets.",
        "operationId": "provisionContractWallet",
        "security": [{ "ServiceKey": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["chain"], "properties": { "chain": { "type": "string", "enum": ["base-mainnet", "base-sepolia"] }, "recovery_address": { "type": "string", "description": "Optional 0x-prefixed address for auto-drain on day-90 deletion." } } } } }
        },
        "responses": { "201": { "description": "Wallet provisioned" }, "400": { "description": "Unsupported chain or invalid recovery address" }, "402": { "description": "Insufficient cash balance for 30-day prepay" } }
      },
      "get": {
        "tags": ["Contracts"],
        "summary": "List KMS contract wallets for the project",
        "operationId": "listContractWallets",
        "security": [{ "ServiceKey": [] }],
        "responses": { "200": { "description": "Wallet list" } }
      }
    },
    "/contracts/v1/wallets/{id}": {
      "get": {
        "tags": ["Contracts"],
        "summary": "Get a KMS contract wallet by id",
        "operationId": "getContractWallet",
        "security": [{ "ServiceKey": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Wallet metadata + live native balance" }, "404": { "description": "Wallet not found (or wrong project — same response)" } }
      },
      "delete": {
        "tags": ["Contracts"],
        "summary": "Delete a KMS contract wallet (schedules KMS key deletion)",
        "description": "Schedules the KMS key for deletion (7-day AWS minimum window). Requires header X-Confirm-Delete: <wallet_id>. Refused with 409 if balance >= dust — drain the wallet first.",
        "operationId": "deleteContractWallet",
        "security": [{ "ServiceKey": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "X-Confirm-Delete", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Wallet deleted" }, "400": { "description": "Missing/wrong confirmation header" }, "409": { "description": "Wallet has on-chain balance — drain first" } }
      }
    },
    "/contracts/v1/wallets/{id}/recovery-address": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Set or clear the recovery address for auto-drain on deletion",
        "operationId": "setContractWalletRecoveryAddress",
        "security": [{ "ServiceKey": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "recovery_address": { "type": ["string", "null"] } } } } } },
        "responses": { "200": { "description": "Updated wallet" }, "400": { "description": "Invalid address or self-reference" }, "404": { "description": "Wallet not found" }, "410": { "description": "Wallet deleted" } }
      }
    },
    "/contracts/v1/wallets/{id}/alert": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Set the low-balance alert threshold (in wei)",
        "operationId": "setContractWalletAlertThreshold",
        "security": [{ "ServiceKey": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["threshold_wei"], "properties": { "threshold_wei": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Updated wallet" }, "404": { "description": "Wallet not found" } }
      }
    },
    "/contracts/v1/wallets/{id}/drain": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Drain native balance to a destination address (works on suspended wallets)",
        "description": "Drain the wallet's entire native-token balance to a destination address. Works on suspended wallets — the safety valve, so a project that runs out of cash credit can still recover its on-chain funds. Header X-Confirm-Drain: <wallet_id> required. Cost: chain gas at-cost + $0.000005 KMS sign fee.",
        "operationId": "drainContractWallet",
        "security": [{ "ServiceKey": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "X-Confirm-Drain", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["destination_address"], "properties": { "destination_address": { "type": "string" } } } } } },
        "responses": { "202": { "description": "Drain transaction submitted" }, "400": { "description": "Invalid destination or missing confirmation header" }, "409": { "description": "Nothing to drain" }, "410": { "description": "Wallet deleted" } }
      }
    },
    "/contracts/v1/call": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Submit a contract write call",
        "description": "Submit a write call to a smart contract from a KMS wallet. The gateway encodes the call via viem, signs the digest via AWS KMS, and broadcasts to the chain. Idempotent on optional Idempotency-Key header. Cost: chain gas at-cost + $0.000005 KMS sign fee per call. Returns 202 with call_id and tx_hash.",
        "operationId": "submitContractCall",
        "security": [{ "ServiceKey": [] }],
        "parameters": [{ "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["wallet_id", "chain", "contract_address", "abi_fragment", "function_name", "args"], "properties": { "wallet_id": { "type": "string" }, "chain": { "type": "string" }, "contract_address": { "type": "string" }, "abi_fragment": { "type": "array" }, "function_name": { "type": "string" }, "args": { "type": "array" }, "value": { "type": "string", "description": "Optional native-token value in wei (decimal string)" } } } } } },
        "responses": { "202": { "description": "Submitted, returns { call_id, tx_hash, status: pending }" }, "400": { "description": "Invalid request, ABI parse failure, or function not in ABI" }, "402": { "description": "Insufficient native balance, or wallet suspended" }, "404": { "description": "Wallet not found" }, "410": { "description": "Wallet deleted" }, "502": { "description": "RPC broadcast failure" } }
      }
    },
    "/contracts/v1/read": {
      "post": {
        "tags": ["Contracts"],
        "summary": "Read-only contract call (no signing, no billing)",
        "operationId": "readContract",
        "security": [{ "ServiceKey": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["chain", "contract_address", "abi_fragment", "function_name", "args"], "properties": { "chain": { "type": "string" }, "contract_address": { "type": "string" }, "abi_fragment": { "type": "array" }, "function_name": { "type": "string" }, "args": { "type": "array" } } } } } },
        "responses": { "200": { "description": "Decoded result" }, "400": { "description": "Unsupported chain or invalid ABI" }, "502": { "description": "RPC failure" } }
      }
    },
    "/contracts/v1/calls/{id}": {
      "get": {
        "tags": ["Contracts"],
        "summary": "Get contract call status, gas, and receipt",
        "operationId": "getContractCallStatus",
        "security": [{ "ServiceKey": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Call status (pending, confirmed, failed) with gas_used_wei, gas_cost_usd_micros, receipt" }, "404": { "description": "Call not found (or wrong project — same response)" } }
      }
    },
    "/deploy/v1": {
      "get": {
        "tags": [
          "Bundle"
        ],
        "summary": "Bundle deploy info",
        "operationId": "getBundleDeployInfo",
        "responses": {
          "200": {
            "description": "Tier pricing and request body schema",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Bundle"
        ],
        "summary": "Bundle deploy (one-call full-stack app)",
        "operationId": "bundleDeploy",
        "description": "Deploys project + database + migrations + RLS + secrets + functions + site + subdomain in one call. Requires wallet auth, free within tier.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "migrations": {
                    "type": "string"
                  },
                  "rls": {
                    "type": "object"
                  },
                  "secrets": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "functions": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "site": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "subdomain": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "App deployed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/publish": {
      "post": {
        "tags": [
          "Publish"
        ],
        "summary": "Publish app as a forkable version",
        "operationId": "publishAppVersion",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "unlisted",
                      "private"
                    ]
                  },
                  "fork_allowed": {
                    "type": "boolean"
                  },
                  "description": {
                    "type": "string"
                  },
                  "required_secrets": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Version published",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/versions": {
      "get": {
        "tags": [
          "Publish"
        ],
        "summary": "List published versions",
        "operationId": "listVersions",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Version list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "versions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/v1/admin/{id}/versions/{version_id}": {
      "patch": {
        "tags": [
          "Publish"
        ],
        "summary": "Update version metadata",
        "operationId": "updateVersion",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "description": {
                    "type": "string"
                  },
                  "fork_allowed": {
                    "type": "boolean"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "unlisted",
                      "private"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Version updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Publish"
        ],
        "summary": "Delete a published version",
        "operationId": "deleteVersion",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Version deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    },
                    "version_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/apps/v1": {
      "get": {
        "tags": [
          "Publish"
        ],
        "summary": "List public forkable apps",
        "operationId": "listApps",
        "parameters": [
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by tag (repeatable)"
          }
        ],
        "responses": {
          "200": {
            "description": "App list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apps": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/apps/v1/{version_id}": {
      "get": {
        "tags": [
          "Publish"
        ],
        "summary": "Get public app info",
        "operationId": "getApp",
        "parameters": [
          {
            "name": "version_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "App details with fork info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "App not found"
          }
        }
      }
    },
    "/fork/v1": {
      "get": {
        "tags": [
          "Publish"
        ],
        "summary": "Fork endpoint info",
        "operationId": "getForkInfo",
        "responses": {
          "200": {
            "description": "Body schema and auth info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Publish"
        ],
        "summary": "Fork a published app",
        "operationId": "forkApp",
        "description": "Creates a fully independent project from a published app version. Requires wallet auth, free within tier.",
        "security": [
          {
            "walletAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WalletHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/TimestampHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "version_id",
                  "name"
                ],
                "properties": {
                  "version_id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "subdomain": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "App forked — new project created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/email/v1/domains": {
      "post": {
        "tags": ["Email"],
        "summary": "Register a custom sender domain",
        "description": "Register a custom email sending domain for the project. Returns DKIM CNAME records + recommended SPF/DMARC TXT records. Add the DNS records and poll GET for verification status. Once verified, all outbound email sends from your domain.",
        "security": [{"bearerAuth": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain"],
                "properties": {
                  "domain": {"type": "string", "description": "The domain to register (e.g., 'kysigned.com')"}
                }
              }
            }
          }
        },
        "responses": {
          "201": {"description": "Domain registered, DNS records returned"},
          "400": {"description": "Invalid domain, blocklisted, or project has no wallet"},
          "409": {"description": "Project already has a domain, or domain owned by different wallet"}
        }
      },
      "get": {
        "tags": ["Email"],
        "summary": "Check sender domain verification status",
        "description": "Returns the project's custom sender domain with current verification status. Polls SES for pending domains. Returns { domain: null } if no domain registered. The response includes an `inbound` object with `{ enabled, mx_record, mx_verified }` — use it to determine whether inbound email is opt-in-enabled for this domain and whether the required MX record is live in DNS.",
        "security": [{"bearerAuth": []}],
        "responses": {
          "200": {"description": "Domain status (or null if none registered). Includes `inbound: { enabled, mx_record, mx_verified }`."}
        }
      },
      "delete": {
        "tags": ["Email"],
        "summary": "Remove custom sender domain",
        "description": "Remove the project's custom sender domain. Email reverts to sending from mail.run402.com. SES identity is only deleted when no other projects use the domain.",
        "security": [{"bearerAuth": []}],
        "responses": {
          "200": {"description": "Domain removed"},
          "404": {"description": "No sender domain registered"}
        }
      }
    },
    "/email/v1/domains/inbound": {
      "post": {
        "tags": ["Email"],
        "summary": "Enable inbound email on a custom domain",
        "operationId": "enableInbound",
        "description": "Enable inbound email routing on a verified custom sender domain. Adds the domain to the SES receipt rule so replies arrive at <slug>@<your-domain> instead of @mail.run402.com. Requires the domain to be DKIM-verified first. Returns the MX record to add to your DNS.",
        "security": [{"bearerAuth": []}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {"type": "string", "description": "The custom domain to enable inbound on"}
                },
                "required": ["domain"]
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Inbound enabled, returns MX record to add"},
          "400": {"description": "Missing domain"},
          "404": {"description": "Domain not found for this project"},
          "409": {"description": "Domain must be DKIM-verified before enabling inbound"}
        }
      },
      "delete": {
        "tags": ["Email"],
        "summary": "Disable inbound email on a custom domain",
        "operationId": "disableInbound",
        "description": "Disable inbound email routing on a custom domain. Removes the domain from the SES receipt rule. Inbound mail will no longer be delivered to run402.",
        "security": [{"bearerAuth": []}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {"type": "string", "description": "The custom domain to disable inbound on"}
                },
                "required": ["domain"]
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Inbound disabled"},
          "400": {"description": "Missing domain"},
          "404": {"description": "Domain not found for this project"}
        }
      }
    },
    "/mailboxes/v1": {
      "post": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Create a mailbox",
        "operationId": "createMailbox",
        "description": "Creates a project-scoped mailbox at slug@mail.run402.com.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Mailbox slug (e.g. 'myapp' for myapp@mail.run402.com)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Mailbox created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Mailbox"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "List project mailboxes",
        "operationId": "listMailboxes",
        "description": "Lists all mailboxes belonging to the project.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Mailbox list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mailboxes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Mailbox"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/mailboxes/v1/{id}": {
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Get mailbox details",
        "operationId": "getMailbox",
        "description": "Returns details for a specific mailbox.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Mailbox details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Mailbox"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Mailbox not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Delete (tombstone) a mailbox",
        "operationId": "deleteMailbox",
        "description": "Soft-deletes a mailbox. The address is released.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Mailbox deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleted"
                    },
                    "address": {
                      "type": "string",
                      "example": "myapp@mail.run402.com"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/messages": {
      "post": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Send email",
        "operationId": "sendMailboxMessage",
        "description": "Sends an email from the mailbox. Supports two modes: template mode (provide template + variables) or raw HTML mode (provide subject + html). The from_name field optionally sets a display name on the From header.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "format": "email",
                    "description": "Recipient email address"
                  },
                  "template": {
                    "type": "string",
                    "enum": [
                      "project_invite",
                      "magic_link",
                      "notification"
                    ],
                    "description": "Email template to use (template mode)"
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Template variables (template mode)"
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 998,
                    "description": "Email subject line (raw mode)"
                  },
                  "html": {
                    "type": "string",
                    "description": "HTML email body, max 1MB (raw mode)"
                  },
                  "text": {
                    "type": "string",
                    "description": "Optional plaintext fallback; auto-generated from html if omitted (raw mode)"
                  },
                  "from_name": {
                    "type": "string",
                    "maxLength": 78,
                    "description": "Display name for the From header, e.g. 'My App' renders as '\"My App\" <slug@mail.run402.com>'"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailboxMessage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "List sent messages",
        "operationId": "listMailboxMessages",
        "description": "Lists messages sent from the mailbox. Supports cursor-based pagination.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            },
            "description": "Maximum number of messages to return"
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor for pagination (from previous response's next_cursor)"
          }
        ],
        "responses": {
          "200": {
            "description": "Message list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MailboxMessage"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/messages/{messageId}": {
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Get message with replies",
        "operationId": "getMailboxMessage",
        "description": "Returns a specific message including any replies received.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Message ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Message details with replies",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/MailboxMessage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "replies": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "reply_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "from": {
                                "type": "string",
                                "format": "email"
                              },
                              "body": {
                                "type": "string"
                              },
                              "received_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Message not found"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/messages/{messageId}/raw": {
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Get raw RFC-822 bytes of an inbound message",
        "operationId": "getMailboxMessageRaw",
        "description": "Returns the exact DKIM-signed RFC-822 bytes of an inbound message, fetched verbatim from S3 with no parsing, normalization, or modification. Use this endpoint for cryptographic verification (DKIM signature checks, zk-email proofs). The `body_text` field returned by `GET /mailboxes/v1/{id}/messages/{messageId}` is parsed and quoted-content-stripped — it is suitable for display and threading only, NOT for cryptographic verification. Inbound messages only; outbound messages return 404.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Message ID (must reference an inbound message)"
          }
        ],
        "responses": {
          "200": {
            "description": "Raw RFC-822 bytes, byte-identical to the S3 object",
            "content": {
              "message/rfc822": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Mailbox owned by a different project"
          },
          "404": {
            "description": "Message not found, not inbound, or no raw MIME available"
          },
          "413": {
            "description": "Raw MIME exceeds the 10MB size limit"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/webhooks": {
      "post": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Register webhook",
        "operationId": "createMailboxWebhook",
        "description": "Registers a webhook URL to receive mailbox event notifications.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Webhook callback URL"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "delivery",
                        "bounced",
                        "complained",
                        "reply_received"
                      ]
                    },
                    "description": "Events to subscribe to"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "List webhooks",
        "operationId": "listMailboxWebhooks",
        "description": "Lists all webhooks registered on a mailbox.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Mailbox ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/webhooks/{webhook_id}": {
      "get": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Get webhook",
        "operationId": "getMailboxWebhook",
        "description": "Gets a single webhook by ID.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Delete webhook",
        "operationId": "deleteMailboxWebhook",
        "description": "Deletes a webhook. Idempotent — returns 204 whether the webhook existed or not.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted (or already absent)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Update webhook",
        "operationId": "updateMailboxWebhook",
        "description": "Updates a webhook's url and/or events. At least one field required. Events is a full replacement, not a merge.",
        "security": [
          {
            "serviceKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Mailbox ID"
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "New webhook URL"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "delivery",
                        "bounced",
                        "complained",
                        "reply_received"
                      ]
                    },
                    "description": "New events (full replacement)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated webhook",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/mailboxes/v1/{id}/status": {
      "post": {
        "tags": [
          "Mailboxes"
        ],
        "summary": "Admin reactivate suspended mailbox",
        "operationId": "updateMailboxStatus",
        "description": "Reactivates a suspended mailbox. Requires admin auth.",
        "security": [
          {
            "adminKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mailbox ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active"
                    ],
                    "description": "New mailbox status"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mailbox reactivated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "active"
                    },
                    "mailbox_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apikey": {
        "type": "apiKey",
        "in": "header",
        "name": "apikey",
        "description": "Project anon_key (JWT). Returned when you provision a project."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "User access_token from /auth/v1/token. Pass as Authorization: Bearer <token>."
      },
      "serviceKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Project service_key (JWT with service_role). Returned when you provision a project. Pass as Authorization: Bearer <service_key>."
      },
      "adminKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-admin-key",
        "description": "Server admin key. Required for admin operations."
      },
      "x402": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PAYMENT",
        "description": "x402 payment header. The client library (e.g. @anthropic-ai/sdk x402 facilitator) handles this automatically. Payment is in USDC on Base (mainnet eip155:8453 or testnet eip155:84532)."
      },
      "walletAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Run402-Wallet",
        "description": "EIP-4361 wallet authentication. Requires three headers: X-Run402-Wallet (wallet address), X-Run402-Signature (signed message), X-Run402-Timestamp (unix timestamp). The wallet must have an active tier subscription."
      }
    },
    "parameters": {
      "WalletHeader": {
        "name": "X-Run402-Wallet",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Ethereum wallet address (0x...)"
      },
      "SignatureHeader": {
        "name": "X-Run402-Signature",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "EIP-4361 signature of the message 'run402:{method}:{path}:{timestamp}'"
      },
      "TimestampHeader": {
        "name": "X-Run402-Timestamp",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Unix timestamp (must be within 30 seconds of server time)"
      }
    },
    "schemas": {
      "Webhook": {
        "type": "object",
        "properties": {
          "webhook_id": {
            "type": "string",
            "description": "Webhook ID (whk_<ts>_<rand>)"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Callback URL"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "delivery",
                "bounced",
                "complained",
                "reply_received"
              ]
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TierPricing": {
        "type": "object",
        "properties": {
          "prototype": {
            "type": "object",
            "properties": {
              "price": {
                "type": "string",
                "example": "$0.10"
              },
              "lease_days": {
                "type": "integer",
                "example": 7
              },
              "storage_mb": {
                "type": "integer",
                "example": 250
              },
              "api_calls": {
                "type": "integer",
                "example": 500000
              },
              "max_functions": {
                "type": "integer",
                "example": 8
              },
              "description": {
                "type": "string"
              }
            }
          },
          "hobby": {
            "type": "object",
            "properties": {
              "price": {
                "type": "string",
                "example": "$5.00"
              },
              "lease_days": {
                "type": "integer",
                "example": 30
              },
              "storage_mb": {
                "type": "integer",
                "example": 1024
              },
              "api_calls": {
                "type": "integer",
                "example": 5000000
              },
              "max_functions": {
                "type": "integer",
                "example": 25
              },
              "description": {
                "type": "string"
              }
            }
          },
          "team": {
            "type": "object",
            "properties": {
              "price": {
                "type": "string",
                "example": "$20.00"
              },
              "lease_days": {
                "type": "integer",
                "example": 30
              },
              "storage_mb": {
                "type": "integer",
                "example": 10240
              },
              "api_calls": {
                "type": "integer",
                "example": 50000000
              },
              "max_functions": {
                "type": "integer",
                "example": 100
              },
              "description": {
                "type": "string"
              }
            }
          }
        }
      },
      "TierSubscription": {
        "type": "object",
        "properties": {
          "wallet": {
            "type": "string",
            "example": "0x..."
          },
          "action": {
            "type": "string",
            "enum": [
              "subscribe",
              "renew",
              "upgrade",
              "downgrade"
            ],
            "description": "What happened: subscribe (new), renew (extend), upgrade (higher tier), or downgrade (lower tier, prorated refund)"
          },
          "previous_tier": {
            "type": "string",
            "nullable": true,
            "description": "Previous tier (null if first subscription)"
          },
          "tier": {
            "type": "string",
            "enum": [
              "prototype",
              "hobby",
              "team"
            ]
          },
          "lease_started_at": {
            "type": "string",
            "format": "date-time"
          },
          "lease_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "allowance_remaining_usd_micros": {
            "type": "integer"
          }
        }
      },
      "ProjectCreated": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "anon_key": {
            "type": "string",
            "description": "JWT for client-side access (apikey header)"
          },
          "service_key": {
            "type": "string",
            "description": "JWT for admin access (Authorization: Bearer)"
          },
          "schema_slot": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "enum": [
              "prototype",
              "hobby",
              "team"
            ]
          },
          "lease_expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Subdomain": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "deployment_id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "deployment_url": {
            "type": "string",
            "format": "uri"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Mailbox": {
        "type": "object",
        "properties": {
          "mailbox_id": {
            "type": "string",
            "format": "uuid"
          },
          "address": {
            "type": "string",
            "example": "myapp@mail.run402.com"
          },
          "slug": {
            "type": "string"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "deleted"
            ]
          },
          "sends_today": {
            "type": "integer"
          },
          "unique_recipients": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MailboxMessage": {
        "type": "object",
        "properties": {
          "message_id": {
            "type": "string",
            "format": "uuid"
          },
          "to": {
            "type": "string",
            "format": "email"
          },
          "template": {
            "type": "string",
            "enum": [
              "project_invite",
              "magic_link",
              "notification"
            ]
          },
          "status": {
            "type": "string",
            "example": "sent"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "responses": {
      "PaymentRequired": {
        "description": "Payment required. The response includes x402 payment instructions (network, token, amount, payee address). Use an x402-compatible client to automatically handle payment.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "X-PAYMENT header is required"
                },
                "accepts": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "network": {
                        "type": "string",
                        "example": "eip155:84532"
                      },
                      "token": {
                        "type": "string",
                        "example": "USDC"
                      },
                      "amount": {
                        "type": "string"
                      },
                      "payee": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid authentication. For wallet-auth endpoints, provide X-Run402-Wallet, X-Run402-Signature, and X-Run402-Timestamp headers. For project endpoints, provide apikey header (anon_key) or Authorization: Bearer (service_key/access_token).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}
