{
  "openapi": "3.1.0",
  "info": {
    "title": "Hypamoon API",
    "version": "1.0.0",
    "description": "Hypamoon is the infrastructure a chat or an agent builds on for a business: sites and apps (with server code, sign-in and their own database), data and forms, a media library, domains, email from your own domain, phone numbers with an AI receptionist, SMS and chat, payments (see https://hypamoon.com/docs/payments) and agents. The same project is reachable from a connected chat over MCP and from your own code over this REST API.\n\nCalls are inbound, plus outbound calls your assistant makes to people who expect them (POST /calls).\n\nEvery request needs an API key: `Authorization: Bearer hm_live_...`, or `hm_test_...` in test mode. Each key has scopes and a rate limit of 120 requests per 60 seconds.\n\nAn OAuth access token (`hm_oat_...`, from connecting an assistant such as Claude.ai or ChatGPT) works on every /api/v1 route as well, with the scopes the user granted on the consent screen. For your own app, create an API key in the portal instead.\n\nEvery error is `application/problem+json` with a `type` URL that explains the fix (`https://hypamoon.com/docs/errors/<slug>`). Every response carries an `X-Request-Id` header; include it when you ask us for help.\n\nField names are snake_case, except inside a call plan, which is stored exactly as you send it (camelCase). New fields may be added to a response at any time; renaming or removing one requires a new version.",
    "contact": {
      "name": "Hypamoon support",
      "url": "https://hypamoon.com/docs"
    }
  },
  "servers": [
    {
      "url": "https://api.hypamoon.com/api/v1"
    }
  ],
  "security": [
    {
      "ApiKey": []
    },
    {
      "OAuth2": []
    }
  ],
  "tags": [
    {
      "name": "Domains",
      "description": "Your own domain for a site: connect a domain the user owns (nameservers, with existing records copied), or register a new one. DNS records are managed here."
    },
    {
      "name": "Project",
      "description": "The project behind the key and its company details."
    },
    {
      "name": "Billing",
      "description": "The prepaid balance: every SMS, call minute and number is deducted from it, and AI usage (chat replies, agents, the portal assistant) is billed per use. Top up in the portal."
    },
    {
      "name": "Numbers",
      "description": "Search for, order and list phone numbers."
    },
    {
      "name": "Assistant",
      "description": "The voice AI that answers your calls. One per project."
    },
    {
      "name": "Call plans",
      "description": "What happens when your number is called: ring your team, read out a text, hand over to the assistant."
    },
    {
      "name": "Calls",
      "description": "Your calls, inbound and outbound, with transcript and summary. Your assistant can also call one person who expects it, like a customer with an appointment."
    },
    {
      "name": "Webhooks",
      "description": "Receive an event for every call, signed with Hypamoon-Signature."
    },
    {
      "name": "Messages",
      "description": "Transactional SMS to mobile numbers: order confirmations, shipping updates, reminders. Not for marketing."
    },
    {
      "name": "Chat",
      "description": "Text chat with the same assistant that answers your phone: same instructions, languages, opening hours and website. For a chat in your app or website, called from your backend."
    },
    {
      "name": "Chat widget",
      "description": "A chat window on your website without your own backend: one script tag with a public chat token. Manage tokens with your API key (/chat-tokens); the widget itself calls the /public routes from the visitor's browser."
    },
    {
      "name": "Sites",
      "description": "Static sites at https://<slug>.hypamoon.app: send the files, and the site is live. Free, including in test mode. Pair it with a number and the assistant, or the chat widget. A password or an unlisted address shares a site with a few people; for sign-in per user, add an app with auth to the site."
    },
    {
      "name": "Media",
      "description": "The project's media library: photos and files the user uploads once (in the portal or with an upload page), private until you put them on a site with media in POST /sites or PUT /sites/{id}. No bytes go through a chat."
    },
    {
      "name": "Data",
      "description": "The data layer of a project: collections of JSON documents that your agent shapes itself. A contact form on your site, sign-ups, reservations, or facts for the assistant. Free, including in test mode. Forms post to /public/collections/{name}/documents with form.js and the public token."
    },
    {
      "name": "Test mode",
      "description": "Build and test without a real number or real costs: a test key (hm_test_) works on a test project with test numbers and simulated calls."
    },
    {
      "name": "WordPress",
      "description": "Data behind the WordPress login, without passwords: the Hypamoon Connect plugin sends an inventory, the user chooses what moves (in the chat, the portal or the plugin), and the plugin sends only that into private collections of the project."
    },
    {
      "name": "MCP",
      "description": "The Model Context Protocol server, for agents and AI tools."
    },
    {
      "name": "Meta",
      "description": "About the API itself."
    },
    {
      "name": "Agents",
      "description": "Agents that keep working for your project: a trigger, instructions in plain language, a small set of actions on Hypamoon, and limits. Actions that involve money or have an outside effect wait for the owner's approval by default."
    },
    {
      "name": "Apps",
      "description": "Server code on your site: /api on the same address runs your code, isolated, with allowed hosts, secrets from the vault, the project's data and schedules."
    },
    {
      "name": "Email",
      "description": "Email from the project's own domain (an extra module the owner turns on in the portal): sending domains with their DNS records, emails with cc, bcc and attachments, and batches of separate emails, only to people the project already knows."
    },
    {
      "name": "Agencies",
      "description": "For agencies that build for clients: client projects, access requests, owner invitations, who pays, budgets, usage, invoice lines and activity per client, and payment links and subscriptions on your own payment account for a client. Work inside a client project with the Hypamoon-Project header on any other route. For a client: which agencies have access, and revoking it."
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Meta"
        ],
        "summary": "This OpenAPI document",
        "description": "The OpenAPI 3.1 document of the API. Public: no key needed.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An OpenAPI 3.1 document."
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "operationId": "getStatus",
        "tags": [
          "Meta"
        ],
        "summary": "Check whether Hypamoon is working",
        "description": "Whether Hypamoon is working, per component (sites, API, portal, phone, AI receptionist, email, payments), and any reported problem that is not resolved yet. From the public status page (https://status.hypamoon.com); when that page does not answer, a minimal check of our own. Use it when a step fails and you wonder whether Hypamoon is down: a blocked upload_url or a network error from your own environment is not an outage. user_message is a sentence for the user. Public: no key needed.",
        "security": [],
        "responses": {
          "200": {
            "description": "The status.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                },
                "example": {
                  "status": "operational",
                  "source": "status_page",
                  "components": [
                    {
                      "id": "sites",
                      "name": "Sites and apps",
                      "status": "operational"
                    },
                    {
                      "id": "api",
                      "name": "API and chat connection",
                      "status": "operational"
                    },
                    {
                      "id": "portal",
                      "name": "Portal",
                      "status": "operational"
                    },
                    {
                      "id": "phone",
                      "name": "Phone",
                      "status": "operational"
                    },
                    {
                      "id": "ai",
                      "name": "AI receptionist",
                      "status": "operational"
                    },
                    {
                      "id": "mail",
                      "name": "Email",
                      "status": "operational"
                    },
                    {
                      "id": "payments",
                      "name": "Payments",
                      "status": "operational"
                    }
                  ],
                  "incidents": [],
                  "status_page": "https://status.hypamoon.com",
                  "checked_at": "2026-10-01T09:00:00.000Z",
                  "user_message": "Hypamoon is working normally. The problem is likely in your chat app.",
                  "next_step": "If a step failed, read its error and user_message."
                }
              }
            }
          }
        }
      }
    },
    "/project": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "Project"
        ],
        "summary": "Get the project",
        "description": "The project behind the API key: one app, with its own numbers, one assistant, its calls and its webhooks. verification lists the four verification levels with what each still needs and what it unlocks. access says whether this key or connection is limited to some sites and apps (an owner or admin sets that in the portal); a limited key gets only id, name, edition, created_at, mode and access.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The project.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                },
                "example": {
                  "id": "7d4e2b10-3c5a-4f6e-9b8d-1a2b3c4d5e6f",
                  "name": "Acme Plumbing",
                  "edition": "api",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "spending_limit_eur": 100,
                  "numbers_count": 0,
                  "mode": "live",
                  "rails": {
                    "phone": "live",
                    "outbound": "live",
                    "sms": "live",
                    "email": "live",
                    "payments": "live",
                    "domains": "test"
                  },
                  "verification": {
                    "account_type": "business",
                    "levels": [
                      {
                        "level": "test",
                        "status": "approved",
                        "reason": null,
                        "missing": [],
                        "unlocks": [
                          "test_keys",
                          "test_numbers",
                          "simulated_calls"
                        ]
                      },
                      {
                        "level": "company",
                        "status": "not_started",
                        "reason": null,
                        "missing": [
                          "payment_mandate",
                          "payment_authentication",
                          "phone_verification"
                        ],
                        "unlocks": [
                          "live_keys",
                          "national_numbers",
                          "inbound_calls"
                        ]
                      },
                      {
                        "level": "number_documents",
                        "status": "not_started",
                        "reason": null,
                        "missing": [
                          "level.company",
                          "document.proof_of_address",
                          "document.identity",
                          "document.company_registration"
                        ],
                        "unlocks": [
                          "geographic_numbers"
                        ]
                      },
                      {
                        "level": "full",
                        "status": "not_started",
                        "reason": null,
                        "missing": [
                          "level.company",
                          "director_identity",
                          "ubo_confirmation",
                          "review"
                        ],
                        "unlocks": [
                          "outbound_calls",
                          "spending_cap_above_500_eur",
                          "more_concurrent_calls"
                        ]
                      }
                    ]
                  },
                  "access": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Billing"
        ],
        "summary": "Get the prepaid balance",
        "description": "A live project pays in advance: you top up a balance in the portal (https://app.hypamoon.com/balance) and every SMS, call minute and number is deducted from it immediately. This returns the balance, what was spent this calendar month against the spending cap, the automatic top-up settings and the latest 20 transactions, newest first. All amounts are in euro, excluding VAT. Read-only: topping up and the automatic top-up settings are in the portal. With a test key you see the balance of the live project with test: true. Test mode is free, except AI above the free daily test budget (chat replies, agent model calls), which is deducted from this balance.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The balance with the latest transactions.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance_eur": 24.46,
                  "currency": "EUR",
                  "spent_this_month_eur": 0.3,
                  "spend_cap_eur": 100,
                  "auto_reload": {
                    "enabled": true,
                    "active": true,
                    "threshold_eur": 5,
                    "amount_eur": 20,
                    "max_per_month": 10,
                    "used_this_month": 1,
                    "disabled_reason": null,
                    "card": {
                      "brand": "Visa",
                      "last4": "4242",
                      "expiry": "2028-12-31"
                    }
                  },
                  "transactions": [
                    {
                      "id": "6e5d4c3b-2a19-4f8e-8d7c-6b5a4e3d2c1b",
                      "type": "charge",
                      "source": "sms",
                      "amount_eur": -0.3,
                      "balance_after_eur": 24.46,
                      "description": "SMS, 2 segments",
                      "quantity": 2,
                      "unit": "segment",
                      "reference_id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                      "created_at": "2026-10-01T10:02:00.000Z"
                    },
                    {
                      "id": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09",
                      "type": "top_up",
                      "source": "payment",
                      "amount_eur": 20,
                      "balance_after_eur": 24.76,
                      "description": "Top-up",
                      "quantity": null,
                      "unit": null,
                      "reference_id": "b7e5a1c4-2d93-4f60-8a1e-9c3d5b7f2e10",
                      "created_at": "2026-10-01T09:30:00.000Z"
                    }
                  ],
                  "test": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/project/company": {
      "put": {
        "operationId": "putCompany",
        "tags": [
          "Project"
        ],
        "summary": "Set the company or personal details",
        "description": "A phone number is registered to a business or a person. For a business, set the company name, the address with its country, a contact person and the number in its country's business register (kvk_number in the Netherlands, registration_number elsewhere) before you order a number. Without a company, send account_type personal with the user's address and the user as contact: no registration number needed. A national number needs a business; a personal account can order a geographic number. In test mode no registration or VAT number is needed to order a test number. Switching to account_type personal is refused while the project has a national number (account_type_change_blocked); every switch resets the number_documents and full verification levels to not_started. In the EU send vat_number: we check it with VIES before saving (vat_number_invalid when VIES says it is not valid; vat_status unverified when VIES does not respond, and we retry). A country we cannot serve returns country_not_supported. Which numbers you can order is what GET /numbers/available returns. PUT replaces all fields, except registrar_contact_name, which keeps its saved value when left out. When the project has domains registered or transferred through Hypamoon, the contact, email, phone and address also go to each domain's contact at the registry; domain_contacts in the response says per domain updated, unchanged, skipped or failed, with the reason. A change of organization, registration number or (personal account) person is a change of holder: never sent automatically, it needs Hypamoon support.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyInput"
              },
              "example": {
                "company_name": "Acme Plumbing Ltd",
                "registration_number": "01234567",
                "address": {
                  "street": "Main Street",
                  "house_number": "40",
                  "postal_code": "SW1A 1AA",
                  "city": "London",
                  "country": "GB"
                },
                "contact": {
                  "name": "Jane Smith",
                  "phone": "+447700900123"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The company details as saved.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                },
                "example": {
                  "account_type": "business",
                  "company_name": "Acme Plumbing Ltd",
                  "kvk_number": null,
                  "registration_number": "01234567",
                  "vat_number": null,
                  "vat_status": "not_required",
                  "vat_checked_at": null,
                  "address": {
                    "street": "Main Street",
                    "house_number": "40",
                    "postal_code": "SW1A 1AA",
                    "city": "London",
                    "country": "GB"
                  },
                  "contact": {
                    "name": "Jane Smith",
                    "phone": "+447700900123"
                  },
                  "registrar_contact_name": null,
                  "complete": true
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, vat_number_invalid, country_not_supported, account_type_change_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "vat_number_invalid",
              "country_not_supported",
              "account_type_change_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/vat_number_invalid",
                            "https://hypamoon.com/docs/errors/country_not_supported",
                            "https://hypamoon.com/docs/errors/account_type_change_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/numbers": {
      "get": {
        "operationId": "listNumbers",
        "tags": [
          "Numbers"
        ],
        "summary": "List the numbers",
        "description": "The phone numbers of the project, oldest first. Released numbers are left out.",
        "x-hypamoon-scope": "numbers:read",
        "security": [
          {
            "ApiKey": [
              "numbers:read"
            ]
          },
          {
            "OAuth2": [
              "numbers:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The numbers.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NumberList"
                },
                "example": {
                  "data": [
                    {
                      "id": "5f0c9a2e-8b1d-4c3f-a6e7-2d9b0c1e4f53",
                      "e164": "+•••••••••42",
                      "type": "national",
                      "status": "active",
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "test": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "orderNumber",
        "tags": [
          "Numbers"
        ],
        "summary": "Order a number",
        "description": "Orders a number for the project. The Idempotency-Key header is required: when your request times out, send it again with the same key and you receive the same number, never a second one. Before ordering we check the company details, the spending cap, the payment method, a verified phone with one payment confirmed with your bank (iDEAL or a card with 3D Secure; verification_required otherwise), and the prepaid balance; each has its own error. A national number needs a business: a personal account receives business_required, and a business may need its registration number checked against the business register (verification_required with missing kvk_check). A live number is deducted from the balance: the first month when you order it, then on the first of every month. A geographic number also needs the number_documents verification level (see verification in GET /project).",
        "x-hypamoon-scope": "numbers:write",
        "security": [
          {
            "ApiKey": [
              "numbers:write"
            ]
          },
          {
            "OAuth2": [
              "numbers:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "1 to 255 visible ASCII characters without spaces, like a UUID. A new one per order; reuse it when you retry. We remember a key for 24 hours.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7E]+$"
            },
            "example": "0b6c2f5e-first-number"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NumberOrder"
              },
              "example": {
                "area": "020"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The number was ordered. status is active (it works now) or pending (the registration is with the carrier).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderedNumber"
                },
                "example": {
                  "id": "5f0c9a2e-8b1d-4c3f-a6e7-2d9b0c1e4f53",
                  "e164": "+•••••••••42",
                  "type": "national",
                  "status": "active",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "test": false,
                  "from_stock": true
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: idempotency_key_missing, idempotency_key_invalid, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_missing",
              "idempotency_key_invalid",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_missing",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached, payment_mandate_missing, billing_blocked, insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached",
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required, number_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required",
              "number_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required",
                            "https://hypamoon.com/docs/errors/number_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached, idempotency_key_reused, idempotency_request_in_progress, number_unavailable, number_already_yours.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached",
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "number_unavailable",
              "number_already_yours"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/number_unavailable",
                            "https://hypamoon.com/docs/errors/number_already_yours"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, company_details_missing, business_required, spend_cap_missing, regulatory_documents_missing, no_numbers_available, test_number_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "company_details_missing",
              "business_required",
              "spend_cap_missing",
              "regulatory_documents_missing",
              "no_numbers_available",
              "test_number_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/business_required",
                            "https://hypamoon.com/docs/errors/spend_cap_missing",
                            "https://hypamoon.com/docs/errors/regulatory_documents_missing",
                            "https://hypamoon.com/docs/errors/no_numbers_available",
                            "https://hypamoon.com/docs/errors/test_number_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: order_status_unknown, upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "order_status_unknown",
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/order_status_unknown",
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable: service_unavailable, telephony_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "service_unavailable",
              "telephony_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/service_unavailable",
                            "https://hypamoon.com/docs/errors/telephony_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/numbers/available": {
      "get": {
        "operationId": "searchAvailableNumbers",
        "tags": [
          "Numbers"
        ],
        "summary": "Search for numbers to order",
        "description": "Available numbers you can order with POST /numbers. Everything returned can be ordered right now. type=national returns non-geographic numbers that work across the country; numbers from our stock come first and work immediately. Regional numbers (type=geographic) need documents that cannot be uploaded through the API yet. With a test key, it returns free test numbers from +447700900000 to +447700900999 instead (test: true); they never reach the carrier.",
        "x-hypamoon-scope": "numbers:read",
        "security": [
          {
            "ApiKey": [
              "numbers:read"
            ]
          },
          {
            "OAuth2": [
              "numbers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "national or geographic. Without it, the type follows from area: the national prefix or nothing is national.",
            "schema": {
              "type": "string",
              "enum": [
                "national",
                "geographic"
              ]
            },
            "example": "national"
          },
          {
            "name": "area",
            "in": "query",
            "description": "The area code. Required for geographic; leave it out for national.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The available numbers.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvailableNumberList"
                },
                "example": {
                  "data": [
                    {
                      "e164": "+•••••••••42",
                      "type": "national",
                      "from_stock": true,
                      "test": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable: service_unavailable, telephony_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "service_unavailable",
              "telephony_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/service_unavailable",
                            "https://hypamoon.com/docs/errors/telephony_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/numbers/{id}": {
      "delete": {
        "operationId": "releaseNumber",
        "tags": [
          "Numbers"
        ],
        "summary": "Release a test number",
        "description": "Test mode only. Removes a test number from the test project, so you can take another one (a test project has at most 3). Its call plan is removed with it; calls and messages are kept. A live key gets 409 number_release_not_supported: releasing a live number cancels it at the carrier for good and is not possible through the API.",
        "x-hypamoon-scope": "numbers:write",
        "security": [
          {
            "ApiKey": [
              "numbers:write"
            ]
          },
          {
            "OAuth2": [
              "numbers:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the number.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The test number was released.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleasedNumber"
                },
                "example": {
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
                  "e164": "+447700900042",
                  "released": true,
                  "test": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: number_release_not_supported.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "number_release_not_supported"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/number_release_not_supported"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/numbers/{id}/call-plan": {
      "get": {
        "operationId": "getCallPlan",
        "tags": [
          "Call plans"
        ],
        "summary": "Get the call plan of a number",
        "description": "The call plan of one number, with its version. The version is also in the ETag header; send it back as If-Match with PUT.",
        "x-hypamoon-scope": "numbers:read",
        "security": [
          {
            "ApiKey": [
              "numbers:read"
            ]
          },
          {
            "OAuth2": [
              "numbers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the number.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The call plan that calls to this number follow: its own plan (source number) or, without one, the plan of the project (source project, version 0).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallPlanResponse"
                },
                "example": {
                  "number_id": "5f0c9a2e-8b1d-4c3f-a6e7-2d9b0c1e4f53",
                  "version": 1,
                  "source": "number",
                  "plan": {
                    "entry": "team",
                    "nodes": {
                      "team": {
                        "id": "team",
                        "type": "forward",
                        "strategy": "sequential",
                        "targets": [
                          {
                            "kind": "external",
                            "e164": "+447700900123"
                          }
                        ],
                        "ringSeconds": 20,
                        "onNoAnswer": "ai"
                      },
                      "ai": {
                        "id": "ai",
                        "type": "ai_agent"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "put": {
        "operationId": "putCallPlan",
        "tags": [
          "Call plans"
        ],
        "summary": "Set the call plan of a number",
        "description": "Replaces the call plan of one number. A plan with an ai_agent node needs the assistant (PUT /assistant; voice AI is always included) and updates the assistant with this plan. Send If-Match with the ETag from GET to avoid overwriting a newer plan.",
        "x-hypamoon-scope": "numbers:write",
        "security": [
          {
            "ApiKey": [
              "numbers:write"
            ]
          },
          {
            "OAuth2": [
              "numbers:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the number.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "description": "The version from the ETag of GET, like \"3\". An outdated version returns 412 version_conflict.",
            "schema": {
              "type": "string"
            },
            "example": "\"1\""
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallPlan"
              },
              "example": {
                "entry": "team",
                "nodes": {
                  "team": {
                    "id": "team",
                    "type": "forward",
                    "strategy": "sequential",
                    "targets": [
                      {
                        "kind": "external",
                        "e164": "+447700900123"
                      }
                    ],
                    "ringSeconds": 20,
                    "onNoAnswer": "ai"
                  },
                  "ai": {
                    "id": "ai",
                    "type": "ai_agent"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The call plan as saved, with its new version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallPlanSaved"
                },
                "example": {
                  "number_id": "5f0c9a2e-8b1d-4c3f-a6e7-2d9b0c1e4f53",
                  "version": 1,
                  "plan": {
                    "entry": "team",
                    "nodes": {
                      "team": {
                        "id": "team",
                        "type": "forward",
                        "strategy": "sequential",
                        "targets": [
                          {
                            "kind": "external",
                            "e164": "+447700900123"
                          }
                        ],
                        "ringSeconds": 20,
                        "onNoAnswer": "ai"
                      },
                      "ai": {
                        "id": "ai",
                        "type": "ai_agent"
                      }
                    }
                  },
                  "assistant_sync": "synced"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: voice_ai_not_enabled.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "voice_ai_not_enabled"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/voice_ai_not_enabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "412": {
            "description": "Precondition Failed: version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: team_targets_not_allowed, forward_target_not_allowed, validation_failed, assistant_not_configured.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "team_targets_not_allowed",
              "forward_target_not_allowed",
              "validation_failed",
              "assistant_not_configured"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/team_targets_not_allowed",
                            "https://hypamoon.com/docs/errors/forward_target_not_allowed",
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/assistant_not_configured"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/assistant": {
      "get": {
        "operationId": "getAssistant",
        "tags": [
          "Assistant"
        ],
        "summary": "Get the assistant",
        "description": "The assistant of the project: its voice, languages, instructions, website and fixed greeting.",
        "x-hypamoon-scope": "assistant:write",
        "security": [
          {
            "ApiKey": [
              "assistant:write"
            ]
          },
          {
            "OAuth2": [
              "assistant:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The assistant.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Assistant"
                },
                "example": {
                  "configured": true,
                  "voice": "ava",
                  "language": "en",
                  "languages": [
                    "en"
                  ],
                  "speech_recognition": "en",
                  "instructions": "You answer for Acme Plumbing. Ask for the address and the problem, and tell the caller we call back within the hour. Never quote prices.",
                  "website": "https://acme-plumbing.example/",
                  "greeting": "You are speaking with the digital assistant of Acme Plumbing. How can I help you?",
                  "synced_at": "2026-10-01T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "put": {
        "operationId": "putAssistant",
        "tags": [
          "Assistant"
        ],
        "summary": "Set the assistant",
        "description": "One assistant per project. PUT replaces all fields; any field you leave out reverts to its default. The greeting is fixed: it comes from your project name and always says the caller is talking to a digital assistant; sending greeting returns 422.",
        "x-hypamoon-scope": "assistant:write",
        "security": [
          {
            "ApiKey": [
              "assistant:write"
            ]
          },
          {
            "OAuth2": [
              "assistant:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssistantInput"
              },
              "example": {
                "language": "en",
                "voice": "ava",
                "instructions": "You answer for Acme Plumbing. Ask for the address and the problem, and tell the caller we call back within the hour. Never quote prices.",
                "website": "https://acme-plumbing.example"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assistant as saved.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssistantSaved"
                },
                "example": {
                  "configured": true,
                  "voice": "ava",
                  "language": "en",
                  "languages": [
                    "en"
                  ],
                  "speech_recognition": "en",
                  "instructions": "You answer for Acme Plumbing. Ask for the address and the problem, and tell the caller we call back within the hour. Never quote prices.",
                  "website": "https://acme-plumbing.example/",
                  "greeting": "You are speaking with the digital assistant of Acme Plumbing. How can I help you?",
                  "synced_at": "2026-10-01T09:00:00.000Z",
                  "sync": "synced",
                  "call_plan": "added",
                  "next_step": "Nothing else is needed: the assistant answers every call, and callers can leave a message if it is not available. To ring a team first, PUT /numbers/{id}/call-plan.",
                  "user_message": "Your assistant is ready and answers every call. If it is ever not available, callers can leave a message."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Conflict: version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/calls": {
      "get": {
        "operationId": "listCalls",
        "tags": [
          "Calls"
        ],
        "summary": "List the calls",
        "description": "The calls of the project, inbound and outbound, newest first, without transcript and summary. Follow next_cursor while has_more is true. A call your assistant made with POST /calls has direction outbound and an outbound object with its status, reference and metadata.",
        "x-hypamoon-scope": "calls:read",
        "security": [
          {
            "ApiKey": [
              "calls:read"
            ]
          },
          {
            "OAuth2": [
              "calls:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Calls per page, default 25. A value above 100 is capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25
            },
            "example": 25
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Only inbound calls, or only outbound calls (the ones your assistant made with POST /calls). Without it, both.",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            },
            "example": "outbound"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of calls.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallList"
                },
                "example": {
                  "data": [
                    {
                      "id": "c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                      "direction": "inbound",
                      "from": "+447700900456",
                      "to": "+•••••••••42",
                      "status": "ai_handled",
                      "created_at": "2026-10-01T09:14:02.000Z",
                      "started_at": "2026-10-01T09:14:02.000Z",
                      "answered_at": "2026-10-01T09:14:03.000Z",
                      "ended_at": "2026-10-01T09:15:37.000Z",
                      "duration_sec": 94,
                      "hangup_cause": "normal_clearing",
                      "has_summary": true,
                      "summary_ready_at": null,
                      "test": false,
                      "outbound": null
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "startCall",
        "tags": [
          "Calls"
        ],
        "summary": "Call someone with your assistant",
        "description": "Your project's assistant calls one person who expects the call: to confirm an appointment, follow up a callback request or agree on a delivery time. Not for cold calling or sales: telemarketing to consumers needs their prior consent under telemarketing law, and numbers in a national do-not-call register must be respected. Only call people who asked for it or have an appointment with you. purpose says why you call and is recorded with the call: transactional (the default: an answer to the person's own question, confirming or reminding of an appointment, an order or delivery status, a callback they asked for) or marketing (anything that sells or promotes). Under Dutch rules a marketing call needs the person's prior consent, since 1 July 2026 also for existing customers: purpose marketing only calls when consent for this number is recorded (POST /call-consents), otherwise consent_required and nobody is called. The assistant always opens by saying it is the digital assistant of your company and asks whether this is a good time; that cannot be switched off. assistant_instructions adds what this call is about, on top of the assistant's own instructions (PUT /assistant). It calls from one of your project's numbers (from, or your first active number), so the person can call back. Calling hours: by default 09:00 to 20:00, Monday to Saturday, in the local time of the person called; calling_window can narrow or move that, but never before 08:00 or after 21:00. Outside those hours the request returns outside_calling_hours with next_window_start. Public holidays are not recognized yet. Limits per project: 5 calls per minute, 100 per day and 2 at the same time (concurrent_call_limit, with Retry-After). A new live project starts lower (10 calls per day, 30 after 7 days and a first top-up, the full limit after 30 days and €25 paid). The same number can be called at most 3 times in 24 hours per project and 10 times across all projects: 429 call_limit_reached with scope per_number or per_number_platform, retry_at and a user_message. A call lasts at most max_duration_sec (default 600, at most 1200). Live: mobile and geographic numbers in a country your project can call; never premium-rate, service, toll-free or short numbers like 112. An unsupported destination returns destination_not_allowed with a reason. A live key needs the full verification level (requested in the portal under Verification) plus the company level (company details, spending cap, payment method, a verified phone and one payment confirmed with your bank: iDEAL or a card with 3D Secure). In live mode, the cost of a call is deducted from your prepaid balance when it ends: the voice AI minutes plus the line minutes, each billed per started minute (the rates are under Billing in the portal), and it counts toward the spending cap. Unanswered, busy and failed calls cost nothing; a call that reaches an answering machine is charged for the line minutes. Before we call, the balance must cover the call at its full max_duration_sec; otherwise the request returns insufficient_balance (or billing_blocked when the call would exceed the spending cap) and nobody is called. Live outbound calling is being switched on step by step: until it is on, a live request gets 503 telephony_unavailable and nobody is called. The response is 201 with the call as GET /calls/{id} returns it, with outbound.status queued or ringing; call.completed (and call.summary.ready when the assistant spoke with someone) follow as webhooks with direction outbound and the outbound object. With a test key nothing is dialed and nothing is charged: call a test number from +447700900000 to +447700900999 and the response contains the end result immediately, with the real webhooks. The number determines the outcome: +447700900991 no_answer, +447700900992 busy, +447700900993 an answering machine, +447700900994 failed, +447700900995 outside_calling_hours; any other test number results in completed with a transcript and a summary. Calling hours do not apply in test mode, except for the number that simulates outside_calling_hours. The Idempotency-Key header is required: a retry with the same key returns the same call and never calls the person twice.",
        "x-hypamoon-scope": "calls:write",
        "security": [
          {
            "ApiKey": [
              "calls:write"
            ]
          },
          {
            "OAuth2": [
              "calls:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "1 to 255 visible ASCII characters without spaces. One per call you intend to make, like appointment-8812-confirm; reuse it when you retry. We remember a key for 24 hours.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7E]+$"
            },
            "example": "appointment-8812-confirm"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallInput"
              },
              "example": {
                "to": "+447700900321",
                "assistant_instructions": "Confirm the appointment of Sam Taylor tomorrow at 10:00 at 40 Main Street. If Sam cannot make it, ask which day suits better and say we will confirm by SMS.",
                "on_voicemail": "leave_message",
                "voicemail_message": "We called about your appointment tomorrow at 10:00. Call us back on this number if you cannot make it.",
                "max_duration_sec": 300,
                "reference": "appointment-8812",
                "metadata": {
                  "customer_id": "cus_4821"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The call was started (live: outbound.status queued or ringing) or, in test mode, simulated to its end result. The same shape as GET /calls/{id}.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallDetail"
                },
                "example": {
                  "id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e",
                  "direction": "outbound",
                  "from": "+•••••••••42",
                  "to": "+447700900321",
                  "status": "ringing",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "started_at": "2026-10-01T09:00:00.000Z",
                  "answered_at": null,
                  "ended_at": null,
                  "duration_sec": null,
                  "hangup_cause": null,
                  "has_summary": false,
                  "summary_ready_at": null,
                  "test": false,
                  "outbound": {
                    "status": "ringing",
                    "answered_by": null,
                    "voicemail_left": false,
                    "on_voicemail": "leave_message",
                    "max_duration_sec": 300,
                    "reference": "appointment-8812",
                    "metadata": {
                      "customer_id": "cus_4821"
                    },
                    "error": null,
                    "purpose": "transactional"
                  },
                  "transcript": null,
                  "summary": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: idempotency_key_missing, idempotency_key_invalid, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_missing",
              "idempotency_key_invalid",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_missing",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, insufficient_balance, billing_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "insufficient_balance",
              "billing_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/insufficient_balance",
                            "https://hypamoon.com/docs/errors/billing_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required, consent_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required",
              "consent_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required",
                            "https://hypamoon.com/docs/errors/consent_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, destination_not_allowed, outside_calling_hours, company_details_missing, spend_cap_missing, assistant_not_configured, number_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "destination_not_allowed",
              "outside_calling_hours",
              "company_details_missing",
              "spend_cap_missing",
              "assistant_not_configured",
              "number_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/destination_not_allowed",
                            "https://hypamoon.com/docs/errors/outside_calling_hours",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing",
                            "https://hypamoon.com/docs/errors/assistant_not_configured",
                            "https://hypamoon.com/docs/errors/number_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, call_limit_reached, concurrent_call_limit.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "call_limit_reached",
              "concurrent_call_limit"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/call_limit_reached",
                            "https://hypamoon.com/docs/errors/concurrent_call_limit"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable: service_unavailable, telephony_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "service_unavailable",
              "telephony_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/service_unavailable",
                            "https://hypamoon.com/docs/errors/telephony_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/calls/{id}": {
      "get": {
        "operationId": "getCall",
        "tags": [
          "Calls"
        ],
        "summary": "Get a call with transcript and summary",
        "description": "One call, with its transcript and summary. The transcript is only here, never in a webhook.",
        "x-hypamoon-scope": "calls:read",
        "security": [
          {
            "ApiKey": [
              "calls:read"
            ]
          },
          {
            "OAuth2": [
              "calls:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the call.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The call.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallDetail"
                },
                "example": {
                  "id": "c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                  "direction": "inbound",
                  "from": "+447700900456",
                  "to": "+•••••••••42",
                  "status": "ai_handled",
                  "created_at": "2026-10-01T09:14:02.000Z",
                  "started_at": "2026-10-01T09:14:02.000Z",
                  "answered_at": "2026-10-01T09:14:03.000Z",
                  "ended_at": "2026-10-01T09:15:37.000Z",
                  "duration_sec": 94,
                  "hangup_cause": "normal_clearing",
                  "has_summary": true,
                  "summary_ready_at": null,
                  "test": false,
                  "outbound": null,
                  "transcript": "Assistant: U spreekt met de digitale assistent van Acme Plumbing. Waarmee kan ik u helpen?\nCaller: My kitchen tap is leaking, can someone come today?",
                  "summary": {
                    "text": "The caller has a leaking kitchen tap and wants someone to come today.",
                    "topics": [
                      "leak",
                      "appointment"
                    ],
                    "sentiment": "neutral",
                    "action_items": [
                      "Call back within the hour to plan a visit."
                    ],
                    "next_step": "Plan a visit today."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/call-consents": {
      "get": {
        "operationId": "listCallConsents",
        "tags": [
          "Calls"
        ],
        "summary": "List recorded consent for marketing calls",
        "description": "The project's recorded consents for marketing calls, newest first, active and revoked. With phone_number only those for that number. The number itself is never stored, only a keyed hash plus its last two digits (phone_number_ending).",
        "x-hypamoon-scope": "calls:read",
        "security": [
          {
            "ApiKey": [
              "calls:read"
            ]
          },
          {
            "OAuth2": [
              "calls:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "phone_number",
            "in": "query",
            "description": "Optional: only the consents for this number, in E.164.",
            "schema": {
              "type": "string",
              "pattern": "^\\+[1-9]\\d{6,14}$"
            },
            "example": "+447700900321"
          }
        ],
        "responses": {
          "200": {
            "description": "The consents.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallConsentList"
                },
                "example": {
                  "data": [
                    {
                      "id": "5b8e2c1d-7a4f-4e3b-9c6d-1f2a3b4c5d6e",
                      "phone_number_ending": "21",
                      "source": "form",
                      "evidence": "Newsletter sign-up form, entry 7f3c2a, box 'call me about offers' ticked",
                      "consented_at": "2026-10-01T09:30:00.000Z",
                      "recorded_at": "2026-10-01T09:30:02.000Z",
                      "revoked_at": null,
                      "status": "active"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "recordCallConsent",
        "tags": [
          "Calls"
        ],
        "summary": "Record consent for marketing calls",
        "description": "Records that one person agreed to receive marketing calls from this project, so POST /calls with purpose marketing may call them. Under Dutch telemarketing rules a sales or promotional call to a consumer needs prior consent; since 1 July 2026 that also applies to existing customers. source says where it came from (form, call, import, manual) and evidence is your reference to the proof (a form entry id, a call id, a file and row), so it can be shown later. Record only consent the person really gave. The number is stored only as a keyed hash plus its last two digits. If valid consent for this number already exists, it is returned unchanged with 200. Only the project owner, or an API key with calls:write, can record consent.",
        "x-hypamoon-scope": "calls:write",
        "security": [
          {
            "ApiKey": [
              "calls:write"
            ]
          },
          {
            "OAuth2": [
              "calls:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallConsentInput"
              },
              "example": {
                "phone_number": "+447700900321",
                "source": "form",
                "evidence": "Newsletter sign-up form, entry 7f3c2a, box 'call me about offers' ticked",
                "consented_at": "2026-10-01T09:30:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Valid consent for this number already existed; it is returned unchanged.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallConsent"
                },
                "example": {
                  "id": "5b8e2c1d-7a4f-4e3b-9c6d-1f2a3b4c5d6e",
                  "phone_number_ending": "21",
                  "source": "form",
                  "evidence": "Newsletter sign-up form, entry 7f3c2a, box 'call me about offers' ticked",
                  "consented_at": "2026-10-01T09:30:00.000Z",
                  "recorded_at": "2026-10-01T09:30:02.000Z",
                  "revoked_at": null,
                  "status": "active"
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallConsent"
                },
                "example": {
                  "id": "5b8e2c1d-7a4f-4e3b-9c6d-1f2a3b4c5d6e",
                  "phone_number_ending": "21",
                  "source": "form",
                  "evidence": "Newsletter sign-up form, entry 7f3c2a, box 'call me about offers' ticked",
                  "consented_at": "2026-10-01T09:30:00.000Z",
                  "recorded_at": "2026-10-01T09:30:02.000Z",
                  "revoked_at": null,
                  "status": "active"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/call-consents/{id}": {
      "delete": {
        "operationId": "revokeCallConsent",
        "tags": [
          "Calls"
        ],
        "summary": "Revoke consent for marketing calls",
        "description": "Revokes a consent: from now on a marketing call to this number returns consent_required. Nothing is deleted; revoked_at is set and the evidence stays. Revoking twice is harmless. Do this as soon as the person says they no longer want to be called. Only the project owner, or an API key with calls:write, can revoke consent.",
        "x-hypamoon-scope": "calls:write",
        "security": [
          {
            "ApiKey": [
              "calls:write"
            ]
          },
          {
            "OAuth2": [
              "calls:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the consent.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The consent, now revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallConsent"
                },
                "example": {
                  "id": "5b8e2c1d-7a4f-4e3b-9c6d-1f2a3b4c5d6e",
                  "phone_number_ending": "21",
                  "source": "form",
                  "evidence": "Newsletter sign-up form, entry 7f3c2a, box 'call me about offers' ticked",
                  "consented_at": "2026-10-01T09:30:00.000Z",
                  "recorded_at": "2026-10-01T09:30:02.000Z",
                  "revoked_at": "2026-10-12T14:00:00.000Z",
                  "status": "revoked"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "tags": [
          "Webhooks"
        ],
        "summary": "List the webhook endpoints",
        "description": "The project's webhook endpoints, oldest first. The secret is never included.",
        "x-hypamoon-scope": "webhooks:write",
        "security": [
          {
            "ApiKey": [
              "webhooks:write"
            ]
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoints.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                },
                "example": {
                  "data": [
                    {
                      "id": "9e8d7c6b-5a49-4382-9716-a5b4c3d2e1f0",
                      "url": "https://your-app.example/hooks/hypamoon",
                      "events": [
                        "call.completed",
                        "call.summary.ready"
                      ],
                      "active": true,
                      "secret_prefix": "whsec_Xk3p",
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "last_success_at": null,
                      "last_failure_at": null,
                      "consecutive_failures": 0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook endpoint",
        "description": "Creates an endpoint that receives the events you choose, signed with Hypamoon-Signature. The secret is in this response only. At most 10 endpoints per project.",
        "x-hypamoon-scope": "webhooks:write",
        "security": [
          {
            "ApiKey": [
              "webhooks:write"
            ]
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              },
              "example": {
                "url": "https://your-app.example/hooks/hypamoon",
                "events": [
                  "call.completed",
                  "call.summary.ready"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The endpoint, with its secret.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreated"
                },
                "example": {
                  "id": "9e8d7c6b-5a49-4382-9716-a5b4c3d2e1f0",
                  "url": "https://your-app.example/hooks/hypamoon",
                  "events": [
                    "call.completed",
                    "call.summary.ready"
                  ],
                  "active": true,
                  "secret_prefix": "whsec_Xk3p",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "last_success_at": null,
                  "last_failure_at": null,
                  "consecutive_failures": 0,
                  "secret": "whsec_Xk3pQ9vT2mLr8sYw4nBc6dFh1jKz5aGe7uIo0pRtVxE"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, invalid_webhook_endpoint.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "invalid_webhook_endpoint"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/invalid_webhook_endpoint"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Conflict: webhook_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "webhook_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/webhook_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook endpoint",
        "description": "Deletes the endpoint, its pending deliveries and its delivery log.",
        "x-hypamoon-scope": "webhooks:write",
        "security": [
          {
            "ApiKey": [
              "webhooks:write"
            ]
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the webhook endpoint.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/webhooks/{id}/enable": {
      "post": {
        "operationId": "enableWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Switch a webhook endpoint back on",
        "description": "An endpoint is switched off after 50 failed deliveries in a row. This switches it back on and resets the counter; events from the time it was off are not resent.",
        "x-hypamoon-scope": "webhooks:write",
        "security": [
          {
            "ApiKey": [
              "webhooks:write"
            ]
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the webhook endpoint.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "example": {
                  "id": "9e8d7c6b-5a49-4382-9716-a5b4c3d2e1f0",
                  "url": "https://your-app.example/hooks/hypamoon",
                  "events": [
                    "call.completed",
                    "call.summary.ready"
                  ],
                  "active": true,
                  "secret_prefix": "whsec_Xk3p",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "last_success_at": null,
                  "last_failure_at": null,
                  "consecutive_failures": 0
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Immediately sends a webhook.test event to the endpoint and reports the result. When it fails, we retry it like any other delivery: up to 8 attempts over about 45 hours, or 3 attempts within about 6 minutes for an endpoint of a test project.",
        "x-hypamoon-scope": "webhooks:write",
        "security": [
          {
            "ApiKey": [
              "webhooks:write"
            ]
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the webhook endpoint.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The outcome of the first delivery.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                },
                "example": {
                  "delivery_id": "3b2a1908-f7e6-4d5c-8b4a-392817160514",
                  "event_id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                  "status": "delivered",
                  "response_status": 204,
                  "error": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: webhook_endpoint_disabled.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "webhook_endpoint_disabled"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/webhook_endpoint_disabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/test/calls": {
      "post": {
        "operationId": "simulateTestCall",
        "tags": [
          "Test mode"
        ],
        "summary": "Simulate a call",
        "description": "Test mode only: runs a simulated call on one of your test numbers, with a transcript and a summary, and sends the real webhooks (call.completed, and call.summary.ready when there is a summary). A simulated caller talks to your own assistant, which answers with the same instructions, knowledge and tools as on a real call; tools are not executed. caller_goal (optional, at most 300 characters) says what the caller wants; without it the caller asks something that fits the scenario (reservation, callback_request, question). The scenario missed is a call nobody picked up: no transcript, no summary. Nothing reaches the carrier. The first 10 simulated calls per day are free; after that each one is deducted from the prepaid balance of your live project, billed per use (402 test_free_replies_used without balance or saved card). Send it with your test key; a live key gets 403 test_key_required. At most 30 simulated calls per hour per test project.",
        "x-hypamoon-scope": "calls:read",
        "security": [
          {
            "ApiKey": [
              "calls:read"
            ]
          },
          {
            "OAuth2": [
              "calls:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestCallInput"
              },
              "example": {
                "number_id": "0d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6",
                "scenario": "question",
                "caller_goal": "Ask whether you are open on Sunday."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The simulated call, as GET /calls/{id} returns it.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallDetail"
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
                  "direction": "inbound",
                  "from": "+447700900456",
                  "to": "+447700900042",
                  "status": "ai_handled",
                  "created_at": "2026-10-01T09:20:00.000Z",
                  "started_at": "2026-10-01T09:18:52.000Z",
                  "answered_at": "2026-10-01T09:18:56.000Z",
                  "ended_at": "2026-10-01T09:20:00.000Z",
                  "duration_sec": 64,
                  "hangup_cause": "normal_clearing",
                  "has_summary": true,
                  "summary_ready_at": "2026-10-01T09:20:00.000Z",
                  "test": true,
                  "outbound": null,
                  "transcript": "Assistant: You are speaking with the digital assistant of Acme Plumbing. How can I help you?\nCaller: Hello, I have a leak under my kitchen sink and I'd like someone to call me back.\nAssistant: I'm sorry to hear that. I'll make sure someone calls you back. What is your name and address?\nCaller: Tom Baker, 40 Main Street.\nAssistant: Thank you, Tom. Is the water still running, or have you been able to shut it off?\nCaller: I closed the tap under the sink, so it's under control for now.\nAssistant: Good. I've noted a leak under the kitchen sink at 40 Main Street. A colleague will call you back on this number within the hour.",
                  "summary": {
                    "text": "Tom Baker has a leak under his kitchen sink at 40 Main Street. He has shut off the water himself and asks to be called back.",
                    "topics": [
                      "leak",
                      "callback request"
                    ],
                    "sentiment": "neutral",
                    "action_items": [
                      "Call Tom Baker back within the hour about the leak at 40 Main Street"
                    ],
                    "next_step": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: test_free_replies_used.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "test_free_replies_used"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/test_free_replies_used"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, test_key_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "test_key_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/test_key_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/messages": {
      "post": {
        "operationId": "sendMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Send an SMS",
        "description": "Sends one transactional SMS: an order confirmation, a shipping update with track and trace, an appointment reminder. Not for marketing: promotional SMS needs the recipient's prior consent under marketing law, and this endpoint is not built for bulk. A message goes to a mobile number in a country your project can text; an unsupported destination returns destination_not_allowed with a reason. It comes from an alphanumeric sender (your project name, or from); your phone numbers cannot send SMS. At most 6 segments per message, 30 messages per minute and 200 per day per project. A new live project starts lower (20 per day, 60 after 7 days and a first top-up, the full limit after 30 days and €25 paid). The same number gets at most 3 messages in 24 hours per project and 10 across all projects: 429 message_limit_reached with scope per_number or per_number_platform, retry_at and a user_message. A live key needs the company verification level (company details, spending cap, payment method, a verified phone and one payment confirmed with your bank: iDEAL or a card with 3D Secure); a business may also need its registration number checked against the business register, and a personal account may need our approval of its identity documents (the number_documents level); otherwise the request returns verification_required. Live messages are deducted from your prepaid balance per segment (the rate is under Billing in the portal; test mode is free) and count toward the spending cap. If the balance is too low for this message, the request returns insufficient_balance; if this month's spending plus this message would exceed the cap, it returns billing_blocked. In both cases nothing is sent. The response says queued; message.delivered or message.failed follows as a webhook, and GET /messages/{id} shows the status. With a test key nothing is sent: use a test number from +447700900000 to +447700900999; the message is delivered immediately, and +447700900999 simulates a failure. Send an Idempotency-Key (one per order event, like order-10432-shipped) so a retry never sends the SMS twice.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional but recommended: 1 to 255 visible ASCII characters without spaces. Reuse it when you retry; the same key within 24 hours returns the same message instead of sending a second one.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7E]+$"
            },
            "example": "order-10432-confirmed"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageInput"
              },
              "example": {
                "to": "+447700900321",
                "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                "reference": "order-10432"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The message was accepted (or, in test mode, simulated).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                  "to": "+447700900321",
                  "from": "Acme Shop",
                  "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                  "segments": 1,
                  "encoding": "gsm7",
                  "status": "queued",
                  "error": null,
                  "reference": "order-10432",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "status_updated_at": "2026-10-01T09:00:00.000Z",
                  "test": true
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: idempotency_key_invalid, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_invalid",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, billing_blocked, insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, destination_not_allowed, message_too_long, sender_not_allowed, company_details_missing, spend_cap_missing.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "destination_not_allowed",
              "message_too_long",
              "sender_not_allowed",
              "company_details_missing",
              "spend_cap_missing"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/destination_not_allowed",
                            "https://hypamoon.com/docs/errors/message_too_long",
                            "https://hypamoon.com/docs/errors/sender_not_allowed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, message_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "message_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/message_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable: service_unavailable, telephony_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "service_unavailable",
              "telephony_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/service_unavailable",
                            "https://hypamoon.com/docs/errors/telephony_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listMessages",
        "tags": [
          "Messages"
        ],
        "summary": "List the messages",
        "description": "The SMS messages the project sent through the API, newest first, with their status. Follow next_cursor while has_more is true.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Messages per page, default 25. A value above 100 is capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25
            },
            "example": 25
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of messages.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageList"
                },
                "example": {
                  "data": [
                    {
                      "id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                      "to": "+447700900321",
                      "from": "Acme Shop",
                      "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                      "segments": 1,
                      "encoding": "gsm7",
                      "status": "queued",
                      "error": null,
                      "reference": "order-10432",
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "status_updated_at": "2026-10-01T09:00:00.000Z",
                      "test": true
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Get a message",
        "description": "One SMS message with its current status: queued, sent, delivered or failed.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the message.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The message.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                  "to": "+447700900321",
                  "from": "Acme Shop",
                  "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                  "segments": 1,
                  "encoding": "gsm7",
                  "status": "queued",
                  "error": null,
                  "reference": "order-10432",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "status_updated_at": "2026-10-01T09:00:00.000Z",
                  "test": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/conversations": {
      "post": {
        "operationId": "startConversation",
        "tags": [
          "Chat"
        ],
        "summary": "Start a chat conversation",
        "description": "Starts a text chat with the assistant of the project: the same assistant that answers your phone, with the same instructions, languages, opening hours and website (set with PUT /assistant). The response contains the fixed greeting as the first message; send the customer's messages with POST /conversations/{id}/messages. Call this from your backend, never from a browser: the API key is a server-side secret. A website chat goes through your own server, which adds the key, or uses the chat widget with a public chat token (POST /chat-tokens). A live key needs the company verification level (company details, spending cap, payment method, a verified phone and one payment confirmed with your bank: iDEAL or a card with 3D Secure). At most 20 new conversations per minute and 1000 per day per project. With a test key the assistant gives real answers: the first 20 replies per day are free, after that each reply is deducted from the prepaid balance of your live project the same way as live, billed per use (402 test_free_replies_used without balance or saved card). At most 25 test conversations per day. Send an Idempotency-Key so a retry returns the same conversation.",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional but recommended: 1 to 255 visible ASCII characters without spaces. The same key within 24 hours returns the same conversation.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7E]+$"
            },
            "example": "session-8841-start"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Every field is optional; an empty object starts a conversation.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationInput"
              },
              "example": {
                "reference": "session-8841",
                "metadata": {
                  "page": "/pricing",
                  "plan": "pro"
                },
                "customer": {
                  "name": "Sam Jones",
                  "email": "sam@example.com"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The conversation, with the greeting as its first message.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationDetail"
                },
                "example": {
                  "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                  "channel": "chat",
                  "status": "open",
                  "reference": "session-8841",
                  "metadata": {
                    "page": "/pricing",
                    "plan": "pro"
                  },
                  "customer": {
                    "name": "Sam Jones",
                    "phone": null,
                    "email": "sam@example.com"
                  },
                  "language": "en",
                  "turns": 0,
                  "max_turns": 30,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "last_message_at": "2026-10-01T09:00:00.000Z",
                  "closed_at": null,
                  "close_reason": null,
                  "has_summary": false,
                  "summary_ready_at": null,
                  "test": false,
                  "messages": [
                    {
                      "id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
                      "role": "assistant",
                      "text": "You are chatting with the digital assistant of Acme Plumbing. How can I help you?",
                      "created_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "summary": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: idempotency_key_invalid, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_invalid",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, billing_blocked, insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, company_details_missing, spend_cap_missing, assistant_not_configured.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "company_details_missing",
              "spend_cap_missing",
              "assistant_not_configured"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing",
                            "https://hypamoon.com/docs/errors/assistant_not_configured"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, conversation_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "conversation_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/conversation_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "get": {
        "operationId": "listConversations",
        "tags": [
          "Chat"
        ],
        "summary": "List the conversations",
        "description": "The project's chat conversations, newest first, without messages. Follow next_cursor while has_more is true.",
        "x-hypamoon-scope": "chat:read",
        "security": [
          {
            "ApiKey": [
              "chat:read"
            ]
          },
          {
            "OAuth2": [
              "chat:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Conversations per page, default 25. A value above 100 is capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25
            },
            "example": 25
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only open or only closed conversations.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of conversations.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationList"
                },
                "example": {
                  "data": [
                    {
                      "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                      "channel": "chat",
                      "status": "closed",
                      "reference": "session-8841",
                      "metadata": {
                        "page": "/pricing",
                        "plan": "pro"
                      },
                      "customer": {
                        "name": "Sam Jones",
                        "phone": null,
                        "email": "sam@example.com"
                      },
                      "language": "en",
                      "turns": 2,
                      "max_turns": 30,
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "last_message_at": "2026-10-01T09:01:05.000Z",
                      "closed_at": "2026-10-01T09:31:05.000Z",
                      "close_reason": "inactive",
                      "has_summary": true,
                      "summary_ready_at": "2026-10-01T09:31:09.000Z",
                      "test": false
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/conversations/{id}": {
      "get": {
        "operationId": "getConversation",
        "tags": [
          "Chat"
        ],
        "summary": "Get a conversation with its messages and summary",
        "description": "One conversation with all its messages, oldest first, and the summary once it is closed. The messages are only here, never in a webhook.",
        "x-hypamoon-scope": "chat:read",
        "security": [
          {
            "ApiKey": [
              "chat:read"
            ]
          },
          {
            "OAuth2": [
              "chat:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the conversation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationDetail"
                },
                "example": {
                  "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                  "channel": "chat",
                  "status": "closed",
                  "reference": "session-8841",
                  "metadata": {
                    "page": "/pricing",
                    "plan": "pro"
                  },
                  "customer": {
                    "name": "Sam Jones",
                    "phone": null,
                    "email": "sam@example.com"
                  },
                  "language": "en",
                  "turns": 2,
                  "max_turns": 30,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "last_message_at": "2026-10-01T09:01:05.000Z",
                  "closed_at": "2026-10-01T09:31:05.000Z",
                  "close_reason": "inactive",
                  "has_summary": true,
                  "summary_ready_at": "2026-10-01T09:31:09.000Z",
                  "test": false,
                  "messages": [
                    {
                      "id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
                      "role": "assistant",
                      "text": "You are chatting with the digital assistant of Acme Plumbing. How can I help you?",
                      "created_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "id": "5f6a7b8c-9d0e-4f1a-b2c3-d4e5f6a7b8c9",
                      "role": "customer",
                      "text": "My kitchen tap is leaking. Can someone come today?",
                      "created_at": "2026-10-01T09:00:20.000Z"
                    },
                    {
                      "id": "6a7b8c9d-0e1f-4a2b-83c4-e5f6a7b8c9d0",
                      "role": "assistant",
                      "text": "I'm sorry about the leak. I can't plan a visit myself, but I'll pass it on: on which number can our team call you back within the hour?",
                      "created_at": "2026-10-01T09:00:22.000Z"
                    }
                  ],
                  "summary": {
                    "text": "Sam Jones has a leaking kitchen tap and wants someone to come today. Sam left the number +447700900321 for a call back.",
                    "topics": [
                      "leak",
                      "visit"
                    ],
                    "sentiment": "neutral",
                    "action_items": [
                      "Call Sam Jones back on +447700900321 within the hour to plan a visit."
                    ],
                    "next_step": "Call back within the hour."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/conversations/{id}/messages": {
      "post": {
        "operationId": "sendChatMessage",
        "tags": [
          "Chat"
        ],
        "summary": "Send a customer message and get the reply",
        "description": "Sends what the customer typed and returns the assistant's reply in the same response. With `Accept: text/event-stream` the reply streams as Server-Sent Events: `delta` events with pieces of text, then one `done` event with the same body as the JSON response (or an `error` event with a problem+json). The text in `done.reply` is the reply as stored; display that text. The customer's text is never treated as an instruction to the assistant, and the assistant never shares its instructions or keys. In a chat the assistant cannot book, transfer or send anything: it takes a message, which ends up in the summary. At most 2000 characters per message, 30 turns per conversation (then it closes) and 12 messages per minute per conversation. In live mode, every assistant reply is billed per use, usually about one cent a reply: first from the AI credit in your plan, then from your prepaid balance (the greeting is free; a reply that fails costs nothing). The summary of a conversation is charged the same way when it closes. In test mode the first replies of the day are free, after that they are deducted from the balance of your live project. Send an Idempotency-Key (one per customer message) so a retry returns the same reply instead of a second turn.",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the conversation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional but recommended: 1 to 255 visible ASCII characters without spaces, a new one per customer message. A retry with the same key within 24 hours returns the same reply.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[\\x21-\\x7E]+$"
            },
            "example": "session-8841-msg-1"
          },
          {
            "name": "Accept",
            "in": "header",
            "description": "text/event-stream streams the reply as Server-Sent Events; anything else returns a single JSON response.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/event-stream"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatMessageInput"
              },
              "example": {
                "text": "My kitchen tap is leaking. Can someone come today?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer's message and the assistant's reply.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatTurn"
                },
                "example": {
                  "message": {
                    "id": "5f6a7b8c-9d0e-4f1a-b2c3-d4e5f6a7b8c9",
                    "role": "customer",
                    "text": "My kitchen tap is leaking. Can someone come today?",
                    "created_at": "2026-10-01T09:00:20.000Z"
                  },
                  "reply": {
                    "id": "6a7b8c9d-0e1f-4a2b-83c4-e5f6a7b8c9d0",
                    "role": "assistant",
                    "text": "I'm sorry about the leak. I can't plan a visit myself, but I'll pass it on: on which number can our team call you back within the hour?",
                    "created_at": "2026-10-01T09:00:22.000Z"
                  },
                  "conversation": {
                    "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                    "channel": "chat",
                    "status": "open",
                    "reference": "session-8841",
                    "metadata": {
                      "page": "/pricing",
                      "plan": "pro"
                    },
                    "customer": {
                      "name": "Sam Jones",
                      "phone": null,
                      "email": "sam@example.com"
                    },
                    "language": "en",
                    "turns": 1,
                    "max_turns": 30,
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "last_message_at": "2026-10-01T09:00:20.000Z",
                    "closed_at": null,
                    "close_reason": null,
                    "has_summary": false,
                    "summary_ready_at": null,
                    "test": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: idempotency_key_invalid, invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_invalid",
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid",
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, billing_blocked, insufficient_balance, test_free_replies_used.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance",
              "test_free_replies_used"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance",
                            "https://hypamoon.com/docs/errors/test_free_replies_used"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conversation_closed, conversation_turn_limit_reached, conversation_busy, idempotency_key_reused, idempotency_request_in_progress.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "conversation_closed",
              "conversation_turn_limit_reached",
              "conversation_busy",
              "idempotency_key_reused",
              "idempotency_request_in_progress"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conversation_closed",
                            "https://hypamoon.com/docs/errors/conversation_turn_limit_reached",
                            "https://hypamoon.com/docs/errors/conversation_busy",
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, company_details_missing, spend_cap_missing.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "company_details_missing",
              "spend_cap_missing"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/conversations/{id}/close": {
      "post": {
        "operationId": "closeConversation",
        "tags": [
          "Chat"
        ],
        "summary": "Close a conversation",
        "description": "Closes the conversation: it accepts no more messages, and the summary follows as conversation.summary.ready and in GET /conversations/{id}. Without a customer message there is no summary. Closing an already closed conversation returns it unchanged. A conversation closes automatically after 30 minutes without messages.",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the conversation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation, closed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                },
                "example": {
                  "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                  "channel": "chat",
                  "status": "closed",
                  "reference": "session-8841",
                  "metadata": {
                    "page": "/pricing",
                    "plan": "pro"
                  },
                  "customer": {
                    "name": "Sam Jones",
                    "phone": null,
                    "email": "sam@example.com"
                  },
                  "language": "en",
                  "turns": 2,
                  "max_turns": 30,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "last_message_at": "2026-10-01T09:01:05.000Z",
                  "closed_at": "2026-10-01T09:31:05.000Z",
                  "close_reason": "closed",
                  "has_summary": false,
                  "summary_ready_at": null,
                  "test": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/chat-tokens": {
      "get": {
        "operationId": "listChatTokens",
        "tags": [
          "Chat widget"
        ],
        "summary": "List the public chat tokens",
        "description": "The project's public chat tokens for the chat widget, newest first, including revoked ones, each with its embed_code. daily_spend_limit_eur is the most the project's public chat may spend on replies per day (default 5 euro, up to 1000); change it in the portal.",
        "x-hypamoon-scope": "chat:read",
        "security": [
          {
            "ApiKey": [
              "chat:read"
            ]
          },
          {
            "OAuth2": [
              "chat:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The tokens and the daily spending limit.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatTokenList"
                },
                "example": {
                  "data": [
                    {
                      "id": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
                      "name": "acme-plumbing.example",
                      "display_name": "Acme Plumbing",
                      "token": "hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE",
                      "mode": "live",
                      "allowed_origins": [
                        "https://www.acme-plumbing.example",
                        "https://acme-plumbing.example"
                      ],
                      "turnstile": true,
                      "max_conversations_per_day": 200,
                      "revoked": false,
                      "revoked_at": null,
                      "last_used_at": null,
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "embed_code": "<script src=\"https://api.hypamoon.com/widget.js\" data-token=\"hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE\" async></script>",
                      "kind": "chat"
                    }
                  ],
                  "daily_spend_limit_eur": 5
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createChatToken",
        "tags": [
          "Chat widget"
        ],
        "summary": "Create a public chat token for the chat widget",
        "description": "Creates a public chat token and returns it with embed_code, the script tag to paste on the website. The widget then chats with the project's assistant (set with PUT /assistant) directly from the visitor's browser, with no backend of your own. The token is not a secret: it is in the page source. It can only chat, only from allowed_origins (exact origins like https://www.example.com, no path and no wildcard), with a bot check and its own limits, and you can revoke it. Your API key stays on the server. A live token needs at least one https origin; a test key creates a test token (hm_pubtest_) that also works from localhost. At most 10 origins per token, and at most 1000 new conversations per day per token (default 200).",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatTokenInput"
              },
              "example": {
                "name": "acme-plumbing.example",
                "allowed_origins": [
                  "https://www.acme-plumbing.example",
                  "https://acme-plumbing.example"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The token, with embed_code.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatToken"
                },
                "example": {
                  "id": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
                  "name": "acme-plumbing.example",
                  "display_name": "Acme Plumbing",
                  "token": "hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE",
                  "mode": "live",
                  "allowed_origins": [
                    "https://www.acme-plumbing.example",
                    "https://acme-plumbing.example"
                  ],
                  "turnstile": true,
                  "max_conversations_per_day": 200,
                  "revoked": false,
                  "revoked_at": null,
                  "last_used_at": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "embed_code": "<script src=\"https://api.hypamoon.com/widget.js\" data-token=\"hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE\" async></script>",
                  "kind": "chat"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/chat-tokens/{id}": {
      "get": {
        "operationId": "getChatToken",
        "tags": [
          "Chat widget"
        ],
        "summary": "Get a public chat token",
        "description": "One public chat token with its embed_code.",
        "x-hypamoon-scope": "chat:read",
        "security": [
          {
            "ApiKey": [
              "chat:read"
            ]
          },
          {
            "OAuth2": [
              "chat:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the public chat token.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The token.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatToken"
                },
                "example": {
                  "id": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
                  "name": "acme-plumbing.example",
                  "display_name": "Acme Plumbing",
                  "token": "hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE",
                  "mode": "live",
                  "allowed_origins": [
                    "https://www.acme-plumbing.example",
                    "https://acme-plumbing.example"
                  ],
                  "turnstile": true,
                  "max_conversations_per_day": 200,
                  "revoked": false,
                  "revoked_at": null,
                  "last_used_at": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "embed_code": "<script src=\"https://api.hypamoon.com/widget.js\" data-token=\"hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE\" async></script>",
                  "kind": "chat"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateChatToken",
        "tags": [
          "Chat widget"
        ],
        "summary": "Change a public chat token",
        "description": "Changes the name, the display name (the business name in the chat window and its greeting), the allowed origins, the bot check or the daily limit of a token. Fields you leave out stay as they are; allowed_origins replaces the whole list. The token itself stays the same, so the snippet on the website keeps working. A revoked token cannot be changed (409 chat_token_revoked): create a new one.",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the public chat token.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatTokenUpdate"
              },
              "example": {
                "allowed_origins": [
                  "https://www.acme-plumbing.example",
                  "https://acme-plumbing.example",
                  "https://shop.acme-plumbing.example"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token as changed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatToken"
                },
                "example": {
                  "id": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
                  "name": "acme-plumbing.example",
                  "display_name": "Acme Plumbing",
                  "token": "hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE",
                  "mode": "live",
                  "allowed_origins": [
                    "https://www.acme-plumbing.example",
                    "https://acme-plumbing.example",
                    "https://shop.acme-plumbing.example"
                  ],
                  "turnstile": true,
                  "max_conversations_per_day": 200,
                  "revoked": false,
                  "revoked_at": null,
                  "last_used_at": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "embed_code": "<script src=\"https://api.hypamoon.com/widget.js\" data-token=\"hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE\" async></script>",
                  "kind": "chat"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: chat_token_revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_token_revoked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_token_revoked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "revokeChatToken",
        "tags": [
          "Chat widget"
        ],
        "summary": "Revoke a public chat token",
        "description": "Revokes the token immediately: the widget on the website starts no new conversations, and open conversations through this token accept no more messages. Cannot be undone. Returns the token with revoked: true; revoking it again returns the same.",
        "x-hypamoon-scope": "chat:write",
        "security": [
          {
            "ApiKey": [
              "chat:write"
            ]
          },
          {
            "OAuth2": [
              "chat:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the public chat token.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The token, revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatToken"
                },
                "example": {
                  "id": "1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b",
                  "name": "acme-plumbing.example",
                  "display_name": "Acme Plumbing",
                  "token": "hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE",
                  "mode": "live",
                  "allowed_origins": [
                    "https://www.acme-plumbing.example",
                    "https://acme-plumbing.example"
                  ],
                  "turnstile": true,
                  "max_conversations_per_day": 200,
                  "revoked": true,
                  "revoked_at": "2026-10-02T08:00:00.000Z",
                  "last_used_at": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "embed_code": "<script src=\"https://api.hypamoon.com/widget.js\" data-token=\"hm_pub_4mXq9TbR2kLw7NcV1sHd8YzE\" async></script>",
                  "kind": "chat"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites": {
      "get": {
        "operationId": "listSites",
        "tags": [
          "Sites"
        ],
        "summary": "List the sites",
        "description": "The project's static sites, newest first, each with its url and its files (without their content). Deleted sites are not listed.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The sites.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteList"
                },
                "example": {
                  "data": [
                    {
                      "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                      "slug": "acme-plumbing",
                      "name": "Acme Plumbing",
                      "url": "https://acme-plumbing.hypamoon.app",
                      "files": [
                        {
                          "path": "/index.html",
                          "content_type": "text/html",
                          "bytes": 272,
                          "updated_at": "2026-10-01T09:00:00.000Z"
                        },
                        {
                          "path": "/style.css",
                          "content_type": "text/css",
                          "bytes": 98,
                          "updated_at": "2026-10-01T09:00:00.000Z"
                        }
                      ],
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "updated_at": "2026-10-01T09:00:00.000Z",
                      "version": "k3Q9vXbT2mLc8Rw1",
                      "password_protected": false,
                      "test": false,
                      "taken_offline": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createSite",
        "tags": [
          "Sites"
        ],
        "summary": "Publish a static site",
        "description": "Creates a static site and publishes it immediately at https://<slug>.hypamoon.app. Free, including in test mode. Send every file the browser loads, in full: at least /index.html, plus css, js, images and fonts as separate files or inline. There is no build step, so send the built output, never React or Vite source. Text files (html, css, js, json, txt, xml, svg, webmanifest, md) as plain text. Images (png, jpg, gif, webp, ico), fonts (woff, woff2), PDFs (pdf), video (mp4, webm) and audio (mp3, m4a) preferably as source_url, a public https URL that Hypamoon fetches, and base64-encoded in content only when there is no URL (never for video or audio). The type follows from the extension, and binary files must be that type in their first bytes. A PDF is served inline (Content-Disposition: inline); PDF, video and audio support Range requests (206), and count as one site request per started MB served. A PDF with JavaScript, a /Launch action, a form that submits to another host, or a brand name with a login or payment link is refused (site_blocked). source_url: Hypamoon fetches the file once from a public https URL on port 443 (no IP address or internal host, at most 3 redirects, each checked again; 10 s per file) within the plan's size limits. Its type must match the path: an image or font is recognized by its bytes, text must be UTF-8 and not a web page where a .css or .js was expected. The fetched file then goes through the same checks as one sent with content. A file that cannot be fetched returns 422 with the reason at files.<i>.source_url, and nothing changes. At most 100 files with source_url per request and 60 such requests per project per hour (429 rate_limited). A path without an extension also resolves to <path>.html and <path>/index.html, so /about serves /about.html. Your own /404.html is the not-found page. At most 5 new sites a day on the Free plan (no limit on the number of sites). At most 50 files, 1 MB per file (also PDF, video and audio) and 5 MB together. Keep one request under 4 MB: send a larger file through an upload link (POST /sites/uploads) and name it here with sha256 and bytes instead of content. Without slug, one is derived from name. Retrying with the same slug returns 409 slug_taken, never a second site. With version history, send message: what the site is, in one short sentence; it is the first entry in its history. preview is only for PUT /sites/{id}: a new site goes live right away. A static site has no logins or server-side code of its own. To share it with a few people, set password (one shared password, entered once per browser) or make it unlisted; paid access gives each buyer a personal sign-in link. For server-side code and sign-in per user, add an app with auth to the site (POST /apps). Not yet: files over the limits. Every deploy is checked before it goes live: input fields for passwords, PIN codes, card numbers or IBANs, forms that send to another site, hidden iframes, redirects to another site, obfuscated scripts, and login fields next to the name of a bank, government service, parcel or payment service or large platform return 422 site_blocked with the reasons. A new project has a lower limit on deploys per hour and on sites in its first week (deploy_limit_reached, site_limit_reached). With password, every visitor without the password sees a password form on every path (401, not indexed, ten attempts per address per ten minutes), and nothing of the site is cached by the CDN.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SiteInput"
              },
              "example": {
                "slug": "acme-plumbing",
                "name": "Acme Plumbing",
                "files": [
                  {
                    "path": "/index.html",
                    "content": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Acme Plumbing</title><link rel=\"stylesheet\" href=\"style.css\"></head><body><h1>Acme Plumbing</h1><p>Leaking tap? Call us.</p></body></html>"
                  },
                  {
                    "path": "/style.css",
                    "content": "body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 3rem auto; padding: 0 1rem; }"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The site, live at url.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing",
                  "name": "Acme Plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": false,
                  "test": false,
                  "taken_offline": null
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the deploy waits for another owner or builder. Nothing has changed on the live site yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "new_site",
                  "site_id": null,
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: slug_taken, plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "slug_taken",
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/slug_taken",
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_too_large, upload_not_received, slug_reserved, site_limit_reached, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_too_large",
              "upload_not_received",
              "slug_reserved",
              "site_limit_reached",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_too_large",
                            "https://hypamoon.com/docs/errors/upload_not_received",
                            "https://hypamoon.com/docs/errors/slug_reserved",
                            "https://hypamoon.com/docs/errors/site_limit_reached",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/uploads": {
      "post": {
        "operationId": "createSiteUploads",
        "tags": [
          "Sites"
        ],
        "summary": "Get upload links for large files",
        "description": "For files too large to send inline (one request to the API stays under 4 MB). Send path, sha256 and bytes for each file; the response contains an upload_url for each file. PUT the exact bytes to it within 15 minutes (curl -T file '<upload_url>'); the upload checks size, SHA-256 and the type of the bytes (422 type_mismatch) and stores the file. Video and audio above the inline limit (paid plans: up to the bytes_per_media limit) can only be uploaded this way. Then deploy with POST /sites or PUT /sites/{id}, naming the file with path, sha256 and bytes instead of content. A file another site of this project already has needs no new upload. The size is checked against the limits of the project's plan. At most 50 files, 1 MB per file (also PDF, video and audio) and 5 MB together.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SiteUploadInput"
              },
              "example": {
                "files": [
                  {
                    "path": "/img/hero.jpg",
                    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                    "bytes": 2483112
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The upload links.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteUploadList"
                },
                "example": {
                  "data": [
                    {
                      "path": "/img/hero.jpg",
                      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                      "bytes": 2483112,
                      "content_type": "image/jpeg",
                      "upload_url": "https://hypamoon.app/_hypamoon/upload?t=eyJvIjoi...",
                      "method": "PUT",
                      "expires_at": "2026-10-06T10:15:00.000Z"
                    }
                  ],
                  "hint": "If your environment cannot reach the upload_url (a sandbox may block it), send images under ~3 MB as base64 in deploy_site content instead, or create a drop link (create_drop_link) so the user uploads the files in a browser. Files that are already online go as source_url in deploy_site (up to 100 per call). For many local files, ask the user to add them at https://app.hypamoon.com/media, then deploy with the same path, sha256 and bytes.",
                  "limits": {
                    "files": 200,
                    "bytes_per_file": 5242880,
                    "bytes_per_site": 52428800,
                    "bytes_per_pdf": 10485760,
                    "bytes_per_media": 10485760
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/drops": {
      "post": {
        "operationId": "createSiteDrop",
        "tags": [
          "Sites"
        ],
        "summary": "Create a drop link where the user uploads files in a browser",
        "description": "For when your environment cannot reach an upload_url (a sandbox in a chat app may block it), or the user has the files and you do not. Returns an upload page, https://app.hypamoon.com/drop/<token>, open for 24 hours or until the user presses Done, for this project (and the site, with site_id). The user opens it without signing in and drags files, a whole folder (folders stay folders under /media/<folder>/) or a zip, or chooses or takes photos on a phone; up to 100 files per link, within the plan's limits. Photos are made web-ready in the browser: a WebP of at most 2048 px plus a 640 px version, and the original is kept privately next to them (only an original larger than the plan allows per file is left out). An SVG (a logo or icon, at most 1 MB) goes on the site as it is, but only when it is plain drawing: an SVG with scripts, event handlers or links outside the file is refused. With site_id, Done puts the files on the site as a new version (site_version in GET /sites/drops/{id}); send only the HTML with keep_existing. Without site_id, GET /sites/drops/{id} lists the files with path, sha256 and bytes; deploy them with POST /sites or PUT /sites/{id} without content. The url is only in this response; the token is stored as a hash. user_message is a sentence to give the user with the link.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SiteDropInput"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The drop link.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteDrop"
                },
                "example": {
                  "id": "3f1c2a9e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
                  "status": "open",
                  "site_id": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "expires_at": "2026-10-02T09:00:00.000Z",
                  "completed_at": null,
                  "images_only": false,
                  "strip": "location",
                  "folder": "portfolio",
                  "max_files": 100,
                  "files": [],
                  "originals_kept": 0,
                  "site_version": null,
                  "deploy_files": [],
                  "url": "https://app.hypamoon.com/drop/hmd_4fT0aQ8xK2mPq7RsV1wYz3Bc5De6Fg9H",
                  "user_message": "Open this link in your browser and drag your files or a whole folder onto the page (on a phone: choose or take photos). Press Done when everything is in, then come back here and tell me. The link works for 24 hours.",
                  "next_step": "Give the user the url and wait until they say they are done. Then call get_drop with drop_id 3f1c2a9e-5b7d-4e8f-9a0b-1c2d3e4f5a6b, and deploy_site with its deploy_files added to files (no content; you may change a path, keep sha256 and bytes). Never send the images through the chat."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/drops/{id}": {
      "get": {
        "operationId": "getSiteDrop",
        "tags": [
          "Sites"
        ],
        "summary": "Get a drop link and the files the user uploaded",
        "description": "status is open (waiting for the user), done (the user pressed Done) or expired (after 24 hours). files lists what arrived (photos with variant 2048 or 640, width and height; private originals are not listed); deploy_files is the same list in the form POST /sites and PUT /sites/{id} take (path, sha256, bytes, no content). With site_id and status done, site_version is the new version of the site that already has these files: send only the HTML. You may give a file another path in the deploy; keep its sha256 and bytes.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the drop link.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The drop link and its files.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteDrop"
                },
                "example": {
                  "id": "3f1c2a9e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
                  "status": "done",
                  "site_id": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "expires_at": "2026-10-02T09:00:00.000Z",
                  "completed_at": "2026-10-01T09:04:00.000Z",
                  "images_only": false,
                  "strip": "location",
                  "folder": "portfolio",
                  "max_files": 100,
                  "files": [
                    {
                      "path": "/media/portfolio/project-12/03.webp",
                      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                      "bytes": 384112,
                      "content_type": "image/webp",
                      "name": "03.jpg",
                      "variant": "2048",
                      "width": 2048,
                      "height": 1365
                    },
                    {
                      "path": "/media/portfolio/project-12/03-640.webp",
                      "sha256": "3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7",
                      "bytes": 41240,
                      "content_type": "image/webp",
                      "name": "03.jpg",
                      "variant": "640",
                      "width": 640,
                      "height": 427
                    }
                  ],
                  "originals_kept": 0,
                  "site_version": null,
                  "deploy_files": [
                    {
                      "path": "/media/portfolio/project-12/03.webp",
                      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                      "bytes": 384112
                    },
                    {
                      "path": "/media/portfolio/project-12/03-640.webp",
                      "sha256": "3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7",
                      "bytes": 41240
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/media": {
      "get": {
        "operationId": "listMedia",
        "tags": [
          "Media"
        ],
        "summary": "List the media library",
        "description": "Every photo and file the user uploaded to the project's media library (in the portal under Media, or with an upload link from POST /media/links), newest first, with the folders. Per file: path under /media/, size, width and height, the 640 version for srcset, alt text, and the sites that use it. A file on no site has url null: it is private until you put it on a site with POST /sites or PUT /sites/{id} and media. Never contains the bytes. folder narrows to a folder (with its subfolders), path to one file, site_id to the files on one site.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "folder",
            "in": "query",
            "description": "Only this folder under /media/, with its subfolders, like portfolio.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "path",
            "in": "query",
            "description": "Only the file with this path, like /media/logo.webp.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "site_id",
            "in": "query",
            "description": "Only the files this site uses.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "At most this many files (default 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The files and the folders.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaList"
                },
                "example": {
                  "data": [
                    {
                      "id": "6c0e3a5b-2f1d-4a8e-9b7c-1d2e3f4a5b6c",
                      "path": "/media/portfolio/project-12/03.webp",
                      "folder": "portfolio/project-12",
                      "name": "03.webp",
                      "original_name": "03.jpg",
                      "content_type": "image/webp",
                      "bytes": 384112,
                      "width": 2048,
                      "height": 1365,
                      "alt": null,
                      "url": null,
                      "variants": {
                        "640": {
                          "path": "/media/portfolio/project-12/03-640.webp",
                          "bytes": 41240,
                          "width": 640,
                          "height": 427,
                          "url": null
                        }
                      },
                      "srcset": null,
                      "original_kept": true,
                      "sites": [],
                      "source": "portaal",
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "folders": [
                    {
                      "folder": "portfolio/project-12",
                      "files": 1
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/media/links": {
      "post": {
        "operationId": "createMediaLink",
        "tags": [
          "Media"
        ],
        "summary": "Create an upload page for the media library",
        "description": "Returns an upload page, https://app.hypamoon.com/drop/<token>, open for 24 hours or until the user presses Done. The user drags files or whole folders, or picks photos on a phone; photos are made web-ready in the browser (WebP at most 2048 px plus a 640 px version) and the original is kept privately. Everything lands in the media library under folder; with site_id, Done also puts the files on that site as a new version. Then list them with GET /media and put them on a site with media in POST /sites or PUT /sites/{id}. The url is only in this response.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaLinkInput"
              },
              "example": {
                "folder": "portfolio"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The upload page.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteDrop"
                },
                "example": {
                  "id": "3f1c2a9e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
                  "status": "open",
                  "site_id": null,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "expires_at": "2026-10-02T09:00:00.000Z",
                  "completed_at": null,
                  "images_only": false,
                  "strip": "location",
                  "folder": "portfolio",
                  "max_files": 100,
                  "files": [],
                  "originals_kept": 0,
                  "site_version": null,
                  "deploy_files": [],
                  "url": "https://app.hypamoon.com/drop/hmd_4fT0aQ8xK2mPq7RsV1wYz3Bc5De6Fg9H",
                  "user_message": "Open this link in your browser and drag your files or a whole folder onto the page (on a phone: choose or take photos). Press Done when everything is in, then come back here and tell me. The link works for 24 hours.",
                  "next_step": "Give the user the url and wait until they say they are done. Then call list_media with folder portfolio and put the files on a site with deploy_site media: [\"portfolio/\"]. Never send the images through the chat."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/media/{id}": {
      "get": {
        "operationId": "getMedia",
        "tags": [
          "Media"
        ],
        "summary": "Get a file from the media library",
        "description": "One file with its versions, alt text and the sites that use it.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaFile"
                },
                "example": {
                  "id": "6c0e3a5b-2f1d-4a8e-9b7c-1d2e3f4a5b6c",
                  "path": "/media/portfolio/project-12/03.webp",
                  "folder": "portfolio/project-12",
                  "name": "03.webp",
                  "original_name": "03.jpg",
                  "content_type": "image/webp",
                  "bytes": 384112,
                  "width": 2048,
                  "height": 1365,
                  "alt": null,
                  "url": null,
                  "variants": {
                    "640": {
                      "path": "/media/portfolio/project-12/03-640.webp",
                      "bytes": 41240,
                      "width": 640,
                      "height": 427,
                      "url": null
                    }
                  },
                  "srcset": null,
                  "original_kept": true,
                  "sites": [],
                  "source": "portaal",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateMedia",
        "tags": [
          "Media"
        ],
        "summary": "Rename or move a file, or set its alt text",
        "description": "name and folder change the path in the library (the extension stays); the 640 version moves with it. A site keeps the file under the path it has until you deploy again. alt sets the alt text; null removes it. 409 conflict when another file already has the new path.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaUpdate"
              },
              "example": {
                "alt": "Apple pie on a wooden board"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The file after the change.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaFile"
                },
                "example": {
                  "id": "6c0e3a5b-2f1d-4a8e-9b7c-1d2e3f4a5b6c",
                  "path": "/media/portfolio/project-12/03.webp",
                  "folder": "portfolio/project-12",
                  "name": "03.webp",
                  "original_name": "03.jpg",
                  "content_type": "image/webp",
                  "bytes": 384112,
                  "width": 2048,
                  "height": 1365,
                  "alt": "Apple pie on a wooden board",
                  "url": null,
                  "variants": {
                    "640": {
                      "path": "/media/portfolio/project-12/03-640.webp",
                      "bytes": 41240,
                      "width": 640,
                      "height": 427,
                      "url": null
                    }
                  },
                  "srcset": null,
                  "original_kept": true,
                  "sites": [],
                  "source": "portaal",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteMedia",
        "tags": [
          "Media"
        ],
        "summary": "Delete a file from the media library",
        "description": "Refused with 409 conflict while a site uses the file (sites lists which, and under which path): remove it from the site first. The stored bytes are removed once nothing refers to them anymore.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}": {
      "get": {
        "operationId": "getSite",
        "tags": [
          "Sites"
        ],
        "summary": "Get a site",
        "description": "One site with its url and its files, without their content.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The site.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing",
                  "name": "Acme Plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": false,
                  "test": false,
                  "taken_offline": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "put": {
        "operationId": "updateSite",
        "tags": [
          "Sites"
        ],
        "summary": "Change the files, the name or the password of a site",
        "description": "Changes the name, the files, the password, or a combination. The slug and the url stay the same. The usual way is a partial update with keep_existing: true: only the files in files are added or replaced, delete_paths removes files, every other file stays, so images already on the site are never sent again. A partial update needs if_match_version, the version from GET; if the site has changed since, or if_match_version is missing, the request returns 409 version_conflict with current_version and nothing changes. Without keep_existing, files is the complete new set of files: a file you leave out is removed, and the response lists those paths in warnings (code files_removed). With version history, send message (what changed and why) so the history reads like a logbook; without it Hypamoon writes a short summary like \"Replaced 6 files with 1, -88% size\". Guardrail: an update without keep_existing that removes more than half of the files, makes the site more than half smaller, or rewrites more than 70% of /index.html while other files disappear, does not go live. It becomes a preview (preview.url, version_id) and the response has guardrail with what would disappear in plain words; the version that was live gets auto_label \"before big change\". Ask the user, then publish the preview with POST /sites/{id}/versions/{versionId}/promote, or send the update again with confirm_replace: true. preview: true makes a preview on purpose. Files can come as source_url instead of content, as with POST /sites. source_url: Hypamoon fetches the file once from a public https URL on port 443 (no IP address or internal host, at most 3 redirects, each checked again; 10 s per file) within the plan's size limits. Its type must match the path: an image or font is recognized by its bytes, text must be UTF-8 and not a web page where a .css or .js was expected. The fetched file then goes through the same checks as one sent with content. A file that cannot be fetched returns 422 with the reason at files.<i>.source_url, and nothing changes. At most 100 files with source_url per request and 60 such requests per project per hour (429 rate_limited). The limits, the abuse checks and four-eyes approval always apply to the whole site as it will be after the update; a 202 requests approval for that whole site. The new version is live as soon as this request returns, for every visitor and as a whole: all files switch together, never a new page with old css. version in the response is the new one. A static site has no logins or server-side code of its own. To share it with a few people, set password (one shared password, entered once per browser) or make it unlisted; paid access gives each buyer a personal sign-in link. For server-side code and sign-in per user, add an app with auth to the site (POST /apps). Not yet: files over the limits. Every deploy is checked before it goes live: input fields for passwords, PIN codes, card numbers or IBANs, forms that send to another site, hidden iframes, redirects to another site, obfuscated scripts, and login fields next to the name of a bank, government service, parcel or payment service or large platform return 422 site_blocked with the reasons. A new project has a lower limit on deploys per hour and on sites in its first week (deploy_limit_reached, site_limit_reached). Setting or changing password logs every visitor out (they enter the new one once); null or an empty string removes it and makes the site open to everyone. Sending the same password again changes nothing. A site that was open may still come from the CDN for up to a minute after its first password is set; after that nothing of it is cached.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SiteUpdate"
              },
              "example": {
                "files": [
                  {
                    "path": "/index.html",
                    "content": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Acme Plumbing</title><link rel=\"stylesheet\" href=\"style.css\"></head><body><h1>Acme Plumbing</h1><p>Leaking tap? Call us.</p></body></html>"
                  },
                  {
                    "path": "/style.css",
                    "content": "body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 3rem auto; padding: 0 1rem; }"
                  }
                ],
                "password": "spring-tour-2027"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The site as it is now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing",
                  "name": "Acme Plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": true,
                  "test": false,
                  "taken_offline": null
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the update waits for another owner or builder. Nothing has changed on the live site yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_update",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_too_large, upload_not_received, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_too_large",
              "upload_not_received",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_too_large",
                            "https://hypamoon.com/docs/errors/upload_not_received",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteSite",
        "tags": [
          "Sites"
        ],
        "summary": "Delete a site",
        "description": "Takes the site offline right away and removes it from the list. Its files and versions are kept for 30 days: POST /sites/{id}/restore puts it back, and GET /sites/deleted lists what can still be restored. After 30 days everything is erased for good. With permanent=true everything is erased right away and the site cannot be restored, also for a site that was already deleted; use it only when the owner explicitly asks for that. The slug stays taken, so an old link never shows someone else's site. Deleting it again returns 204 as well. A site Hypamoon took offline (taken_offline) cannot be deleted: 403 site_offline.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "permanent",
            "in": "query",
            "description": "true erases the site and its files right away, without the 30 days to restore it. Only when the owner explicitly asks for it.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Four-eyes approval is on: the deletion waits for another owner or builder. Nothing has changed on the live site yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_delete",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 0,
                  "bytes": 0,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "204": {
            "description": "Deleted. Without permanent the site can be restored for 30 days.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/deleted": {
      "get": {
        "operationId": "listDeletedSites",
        "tags": [
          "Sites"
        ],
        "summary": "List the deleted sites that can be restored",
        "description": "The sites deleted in the last 30 days that can still be restored with POST /sites/{id}/restore, most recently deleted first, each with restore_until. A site deleted with permanent=true is not listed.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted sites.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedSiteList"
                },
                "example": {
                  "data": [
                    {
                      "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                      "slug": "acme-plumbing",
                      "name": "Acme Plumbing",
                      "deleted_at": "2026-10-14T09:00:00.000Z",
                      "restore_until": "2026-11-13T09:00:00.000Z",
                      "test": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/restore": {
      "post": {
        "operationId": "restoreSite",
        "tags": [
          "Sites"
        ],
        "summary": "Restore a deleted site",
        "description": "Puts a site that was deleted in the last 30 days back live at its old address, with its files, password, visibility and versions. Not restored: an app that ran on the site (deploy it again) and a custom domain (connect it again); restore_notes says so. The plan's site limit applies, as for a new site. A site that was deleted permanently, or more than 30 days ago, cannot be restored (409).",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The site, live again.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestoredSite"
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing",
                  "name": "Acme Plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": false,
                  "test": false,
                  "taken_offline": null,
                  "restore_notes": [
                    "A custom domain was disconnected from the site; connect it again with connect_domain."
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: site_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "site_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/site_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/address-suggestions": {
      "get": {
        "operationId": "suggestSiteAddress",
        "tags": [
          "Sites"
        ],
        "summary": "Suggest an address for a site",
        "description": "Checks the address the user wants (slug) and suggests up to three available ones based on name. Use it before POST /sites to show the user a suggestion and let them pick or change it. Nothing is reserved. Names that look like a bank, login, payment or support page, a brand, or anything with hypamoon in it are refused.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "The name of the site or business, like Acme Plumbing.",
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "example": "Acme Plumbing"
          },
          {
            "name": "slug",
            "in": "query",
            "description": "The address the user would like, like acme-plumbing.",
            "schema": {
              "type": "string",
              "maxLength": 63
            },
            "example": "acme"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested address, checked, and available suggestions.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "requested": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "slug": {
                          "type": "string",
                          "description": "The address you asked about, normalised (lowercase)."
                        },
                        "available": {
                          "type": "boolean",
                          "description": "true when you can use it now: nobody has it, or it is an old address of a site of this project."
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Why not, when available is false."
                        },
                        "url": {
                          "type": "string",
                          "description": "The address it would get."
                        }
                      },
                      "required": [
                        "slug",
                        "available",
                        "reason",
                        "url"
                      ],
                      "additionalProperties": false,
                      "description": "The slug you sent, checked. null without slug."
                    },
                    "suggestions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "slug",
                          "url"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Up to three free addresses derived from name. Nothing is reserved: whoever uses one first gets it."
                    }
                  },
                  "required": [
                    "requested",
                    "suggestions"
                  ],
                  "additionalProperties": false,
                  "description": "Address suggestions for a site."
                },
                "example": {
                  "requested": {
                    "slug": "acme",
                    "available": false,
                    "reason": "Taken by another site.",
                    "url": "https://acme.hypamoon.app"
                  },
                  "suggestions": [
                    {
                      "slug": "acme-plumbing",
                      "url": "https://acme-plumbing.hypamoon.app"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/address": {
      "put": {
        "operationId": "updateSiteAddress",
        "tags": [
          "Sites"
        ],
        "summary": "Change the address or visibility of a site",
        "description": "Renames a site (slug) and/or switches it between public and unlisted. After a rename the old address redirects (301) to the new one for 90 days, then stays reserved for this project for 30 more days without redirecting, so nobody can grab it right away; after that it is free again. At most 2 old addresses redirect at a time (a new rename stops the oldest), every redirect goes straight to the current address, and a site that is offline, unlisted or deleted has no redirects. A site's address can change at most 3 times per 30 days, and at most 10 times per project per 30 days: past that the answer is 429 rate_limited with retry_at and a user_message that says when it can change again. A connected domain stays on the site, and public tokens keep working on every redirecting address of the same site. unlisted puts the site on a secret link (url in the response, 26 random characters, no name in it): the readable address and old addresses give 404, pages are sent with noindex and no-referrer. A secret link is not a login: anyone who has it can open the site. For real protection set a password as well (PUT /sites/{id}). A site on a domain cannot be unlisted. Four-eyes approval applies, just as for a deploy.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "maxLength": 63,
                    "description": "The new address: https://<slug>.hypamoon.app, 3 to 40 characters, lowercase letters, digits and hyphens. The old address keeps redirecting (301) to the new one and stays reserved for this project, so nobody else can take it. A connected domain stays on the site."
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "unlisted"
                    ],
                    "description": "public: the site lives at https://<slug>.hypamoon.app. unlisted: the site only lives at a secret link (https://<random>.hypamoon.app, 26 random characters, no name in it), the readable address gives 404, and search engines are told not to index it. A secret link is not a login: anyone who has the link can open the site. For real protection set a password as well."
                  }
                },
                "additionalProperties": false,
                "description": "Change the address of a site, its visibility, or both."
              },
              "example": {
                "slug": "acme-plumbing-rotterdam"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The site with its new address.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the site. Use it to update or delete the site."
                    },
                    "slug": {
                      "type": "string",
                      "description": "The first part of the address: https://<slug>.hypamoon.app."
                    },
                    "name": {
                      "type": "string",
                      "description": "Your name for the site."
                    },
                    "url": {
                      "type": "string",
                      "description": "Where the site lives: https://<slug>.hypamoon.app, or the secret link when visibility is unlisted."
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SiteFile"
                      },
                      "description": "The files of the site, sorted by path, without their content."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the site was created."
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the site or its files last changed."
                    },
                    "version": {
                      "type": "string",
                      "description": "The version that is live now. Changes on every update; every file on the site carries it as its ETag, so you can check that a page shows this version."
                    },
                    "password_protected": {
                      "type": "boolean",
                      "description": "true when visitors have to enter the site's password first (set with password on POST /sites or PUT /sites/{id}). The password itself is never returned."
                    },
                    "test": {
                      "type": "boolean",
                      "description": "true for a site of a test project. It is public all the same."
                    },
                    "taken_offline": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When Hypamoon took the site offline."
                        },
                        "reason": {
                          "type": "string",
                          "description": "Why, in the words the owner also received by email."
                        }
                      },
                      "required": [
                        "at",
                        "reason"
                      ],
                      "additionalProperties": false,
                      "description": "null while the site is live. Set when Hypamoon took the site offline because of abuse: visitors get a page that says it is unavailable, the files are kept, and PUT and DELETE return 403 site_offline. To object, the owner replies to the email or writes to hello@hypamoon.com."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "const": "files_removed",
                                "description": "A deploy without keep_existing removed files that were on the site."
                              },
                              "message": {
                                "type": "string",
                                "description": "What happened and what to do if it was not intended."
                              },
                              "paths": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The paths that are gone."
                              }
                            },
                            "required": [
                              "code",
                              "message",
                              "paths"
                            ],
                            "additionalProperties": false
                          },
                          {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "const": "build_hint",
                                "description": "Something on the site looks like it works but does not: a form that saves nowhere, localStorage used as a database, a password checked in JavaScript, a button to # or javascript:, a cart without a checkout, test wording on the live project, or a map from a service that needs a key the site does not have. Advice only: the deploy succeeded."
                              },
                              "hint": {
                                "type": "string",
                                "description": "Which hint, like form_no_target, local_storage_db or cart_without_checkout."
                              },
                              "message": {
                                "type": "string",
                                "description": "What does not work, in plain words."
                              },
                              "paths": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The files the hint is about."
                              },
                              "next_step": {
                                "type": "string",
                                "description": "What to do next, with the tool to use."
                              }
                            },
                            "required": [
                              "code",
                              "hint",
                              "message",
                              "paths",
                              "next_step"
                            ],
                            "additionalProperties": false
                          }
                        ],
                        "discriminator": {
                          "propertyName": "code"
                        }
                      },
                      "description": "Only in the answer of a deploy, and only when there is something to tell: files a deploy without keep_existing removed, and build hints. Warnings never block a deploy."
                    },
                    "visibility": {
                      "type": "string",
                      "enum": [
                        "public",
                        "unlisted"
                      ],
                      "description": "public: the site lives at https://<slug>.hypamoon.app. unlisted: url is the secret link (https://<random>.hypamoon.app) and the readable address gives 404."
                    }
                  },
                  "required": [
                    "id",
                    "slug",
                    "name",
                    "url",
                    "files",
                    "created_at",
                    "updated_at",
                    "version",
                    "password_protected",
                    "test",
                    "taken_offline",
                    "visibility"
                  ],
                  "additionalProperties": false,
                  "description": "A static site on hypamoon.app, with its visibility."
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing-rotterdam",
                  "name": "Acme Plumbing",
                  "url": "https://acme-plumbing-rotterdam.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": false,
                  "test": false,
                  "taken_offline": null,
                  "visibility": "public"
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the change waits for another owner or builder. Nothing has changed yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_update",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: slug_taken, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "slug_taken",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/slug_taken",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, slug_reserved, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "slug_reserved",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/slug_reserved",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/rotate-secret-url": {
      "post": {
        "operationId": "rotateSiteSecretUrl",
        "tags": [
          "Sites"
        ],
        "summary": "Make a new secret link for an unlisted site",
        "description": "Gives an unlisted site a new secret link; the old one stops working within seconds, everywhere. Use it when the link reached the wrong people. Takes effect immediately, even when four-eyes approval is on.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The site with its new secret link in url.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the site. Use it to update or delete the site."
                    },
                    "slug": {
                      "type": "string",
                      "description": "The first part of the address: https://<slug>.hypamoon.app."
                    },
                    "name": {
                      "type": "string",
                      "description": "Your name for the site."
                    },
                    "url": {
                      "type": "string",
                      "description": "Where the site lives: https://<slug>.hypamoon.app, or the secret link when visibility is unlisted."
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SiteFile"
                      },
                      "description": "The files of the site, sorted by path, without their content."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the site was created."
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the site or its files last changed."
                    },
                    "version": {
                      "type": "string",
                      "description": "The version that is live now. Changes on every update; every file on the site carries it as its ETag, so you can check that a page shows this version."
                    },
                    "password_protected": {
                      "type": "boolean",
                      "description": "true when visitors have to enter the site's password first (set with password on POST /sites or PUT /sites/{id}). The password itself is never returned."
                    },
                    "test": {
                      "type": "boolean",
                      "description": "true for a site of a test project. It is public all the same."
                    },
                    "taken_offline": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When Hypamoon took the site offline."
                        },
                        "reason": {
                          "type": "string",
                          "description": "Why, in the words the owner also received by email."
                        }
                      },
                      "required": [
                        "at",
                        "reason"
                      ],
                      "additionalProperties": false,
                      "description": "null while the site is live. Set when Hypamoon took the site offline because of abuse: visitors get a page that says it is unavailable, the files are kept, and PUT and DELETE return 403 site_offline. To object, the owner replies to the email or writes to hello@hypamoon.com."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "const": "files_removed",
                                "description": "A deploy without keep_existing removed files that were on the site."
                              },
                              "message": {
                                "type": "string",
                                "description": "What happened and what to do if it was not intended."
                              },
                              "paths": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The paths that are gone."
                              }
                            },
                            "required": [
                              "code",
                              "message",
                              "paths"
                            ],
                            "additionalProperties": false
                          },
                          {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "const": "build_hint",
                                "description": "Something on the site looks like it works but does not: a form that saves nowhere, localStorage used as a database, a password checked in JavaScript, a button to # or javascript:, a cart without a checkout, test wording on the live project, or a map from a service that needs a key the site does not have. Advice only: the deploy succeeded."
                              },
                              "hint": {
                                "type": "string",
                                "description": "Which hint, like form_no_target, local_storage_db or cart_without_checkout."
                              },
                              "message": {
                                "type": "string",
                                "description": "What does not work, in plain words."
                              },
                              "paths": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The files the hint is about."
                              },
                              "next_step": {
                                "type": "string",
                                "description": "What to do next, with the tool to use."
                              }
                            },
                            "required": [
                              "code",
                              "hint",
                              "message",
                              "paths",
                              "next_step"
                            ],
                            "additionalProperties": false
                          }
                        ],
                        "discriminator": {
                          "propertyName": "code"
                        }
                      },
                      "description": "Only in the answer of a deploy, and only when there is something to tell: files a deploy without keep_existing removed, and build hints. Warnings never block a deploy."
                    },
                    "visibility": {
                      "type": "string",
                      "enum": [
                        "public",
                        "unlisted"
                      ],
                      "description": "public: the site lives at https://<slug>.hypamoon.app. unlisted: url is the secret link (https://<random>.hypamoon.app) and the readable address gives 404."
                    }
                  },
                  "required": [
                    "id",
                    "slug",
                    "name",
                    "url",
                    "files",
                    "created_at",
                    "updated_at",
                    "version",
                    "password_protected",
                    "test",
                    "taken_offline",
                    "visibility"
                  ],
                  "additionalProperties": false,
                  "description": "A static site on hypamoon.app, with its visibility."
                },
                "example": {
                  "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "slug": "acme-plumbing",
                  "name": "Acme Plumbing",
                  "url": "https://k7q2m4x9r3t6w8y2b5c7d9f3gh.hypamoon.app",
                  "files": [
                    {
                      "path": "/index.html",
                      "content_type": "text/html",
                      "bytes": 272,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "path": "/style.css",
                      "content_type": "text/css",
                      "bytes": 98,
                      "updated_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "version": "k3Q9vXbT2mLc8Rw1",
                  "password_protected": false,
                  "test": false,
                  "taken_offline": null,
                  "visibility": "unlisted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/versions": {
      "get": {
        "operationId": "listSiteVersions",
        "tags": [
          "Sites"
        ],
        "summary": "List the versions of a site",
        "description": "The history of a site, newest first: every deploy that changed its files (including partial deploys, imports, approved deploys, rollbacks and promoted previews) is a version with who made it, through which client, its message and the changed paths. Previews are in the list too (preview: true, with preview_url). Versions older than the plan's retention period are removed, unless they have a label or are a checkpoint (auto_label \"before big change\"). auto_label also marks versions that stayed live for at least 24 hours and were not rolled back (\"stable\"). Included on every plan; the plan sets the retention period and the number of labels.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "How many versions per page (default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The versions, newest first.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "const": "site_version"
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the version: use it for get_site_diff, rollback_site, promote_site_version and label_site_version."
                          },
                          "number": {
                            "type": "integer",
                            "description": "The number of the version within the site: 1, 2, 3 and so on."
                          },
                          "site_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "When this version was made (ISO 8601)."
                          },
                          "cause": {
                            "type": "string",
                            "enum": [
                              "baseline",
                              "deploy",
                              "partial_deploy",
                              "import",
                              "approved_deploy",
                              "rollback",
                              "promote",
                              "preview",
                              "file_restore"
                            ],
                            "description": "What made it: a deploy, a partial deploy (keep_existing), an import, a deploy after four-eyes approval, a rollback, a promoted preview, a preview, a few files restored from an older version (file_restore), or baseline (how the site was when its history started)."
                          },
                          "message": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The message sent with the deploy, if any."
                          },
                          "author": {
                            "type": "object",
                            "properties": {
                              "user_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "via": {
                                "type": "string",
                                "enum": [
                                  "portal",
                                  "api_key",
                                  "connected_app",
                                  "system"
                                ]
                              },
                              "client": {
                                "type": "string",
                                "enum": [
                                  "chatgpt",
                                  "claude",
                                  "api",
                                  "portal",
                                  "wizard",
                                  "other"
                                ]
                              },
                              "client_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "user_id",
                              "via",
                              "client",
                              "client_name"
                            ],
                            "description": "Who made it, and through which client."
                          },
                          "changes": {
                            "type": "object",
                            "properties": {
                              "added": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "modified": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "removed": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "counts": {
                                "type": "object",
                                "properties": {
                                  "added": {
                                    "type": "integer"
                                  },
                                  "modified": {
                                    "type": "integer"
                                  },
                                  "removed": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "added",
                                  "modified",
                                  "removed"
                                ]
                              },
                              "truncated": {
                                "type": "boolean",
                                "description": "true: the lists show the first 50 paths per kind; get_site_diff has all."
                              }
                            },
                            "required": [
                              "added",
                              "modified",
                              "removed",
                              "counts",
                              "truncated"
                            ],
                            "description": "The paths that changed compared to the version before."
                          },
                          "files": {
                            "type": "integer"
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "current": {
                            "type": "boolean",
                            "description": "true for the version that is live now."
                          },
                          "preview": {
                            "type": "boolean",
                            "description": "true for a preview that never went live."
                          },
                          "preview_url": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The secret address of a preview (not indexed). Share it only with people who should see it."
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "labeled_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "restored_from": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "For a rollback or promote: the version it came from."
                          },
                          "expires_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When the retention of the plan removes it; null for the current version, labeled versions and checkpoints."
                          },
                          "auto_label": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "before big change",
                              "stable",
                              "held by guardrail",
                              null
                            ],
                            "description": "A marker Hypamoon sets itself, apart from label: \"before big change\" is the version that was live when a deploy hit the guardrail (kept like a labeled version), \"stable\" a version that stayed live for at least 24 hours and was not rolled back (both good versions to go back to), and \"held by guardrail\" a preview the guardrail made instead of putting a deploy live."
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "number",
                          "site_id",
                          "created_at",
                          "cause",
                          "message",
                          "author",
                          "changes",
                          "files",
                          "bytes",
                          "current",
                          "preview",
                          "preview_url",
                          "label",
                          "labeled_at",
                          "restored_from",
                          "expires_at",
                          "auto_label"
                        ],
                        "description": "One version of a site: a snapshot of its files."
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Send as cursor for the next page."
                    },
                    "retention_days": {
                      "type": "integer",
                      "description": "How long the plan keeps versions."
                    },
                    "labels_max": {
                      "type": "integer",
                      "description": "How many labeled versions a site can have on this plan."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor",
                    "retention_days",
                    "labels_max"
                  ],
                  "description": "The versions of a site, newest first."
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "site_version",
                      "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                      "number": 4,
                      "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                      "created_at": "2026-10-06T09:12:00Z",
                      "cause": "partial_deploy",
                      "message": "New opening hours",
                      "author": {
                        "user_id": "5b1d6f3e-9a0c-4f2e-8d7b-2c4a1e9f0b33",
                        "via": "connected_app",
                        "client": "claude",
                        "client_name": "Claude"
                      },
                      "changes": {
                        "added": [],
                        "modified": [
                          "/index.html"
                        ],
                        "removed": [],
                        "counts": {
                          "added": 0,
                          "modified": 1,
                          "removed": 0
                        },
                        "truncated": false
                      },
                      "files": 3,
                      "bytes": 18240,
                      "current": true,
                      "preview": false,
                      "preview_url": null,
                      "label": null,
                      "labeled_at": null,
                      "restored_from": null,
                      "expires_at": null,
                      "auto_label": null
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null,
                  "retention_days": 30,
                  "labels_max": 5
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/diff": {
      "get": {
        "operationId": "getSiteDiff",
        "tags": [
          "Sites"
        ],
        "summary": "Show what changed between two versions",
        "description": "For each path, whether it was added, modified, removed or replaced between two versions of a site. Text files (html, css, js, json, txt, svg) get a unified diff; binary files (images, fonts) only their old and new size. Without to: the current version. Without from: the version before to (for a preview: the live version). Large diffs are left out (diff_omitted) to keep the response small.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "The id or number of the older version.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The id or number of the newer version.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What changed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "site_diff"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "from": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "number": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "id",
                        "number"
                      ]
                    },
                    "to": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "number": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "id",
                        "number"
                      ]
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "added": {
                          "type": "integer"
                        },
                        "modified": {
                          "type": "integer"
                        },
                        "removed": {
                          "type": "integer"
                        },
                        "replaced": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "added",
                        "modified",
                        "removed",
                        "replaced"
                      ]
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "added",
                              "modified",
                              "removed",
                              "replaced"
                            ],
                            "description": "replaced: a binary file (image, font) with other bytes."
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "text",
                              "binary"
                            ]
                          },
                          "old_bytes": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "new_bytes": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "old_type": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "new_type": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "diff": {
                            "type": "string",
                            "description": "A unified diff, for text files (html, css, js, json, txt, svg)."
                          },
                          "diff_omitted": {
                            "type": "string",
                            "enum": [
                              "too_large",
                              "response_limit",
                              "unavailable"
                            ]
                          }
                        },
                        "required": [
                          "path",
                          "status",
                          "kind",
                          "old_bytes",
                          "new_bytes"
                        ]
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true: some diffs were left out to keep the answer small (diff_omitted per file)."
                    }
                  },
                  "required": [
                    "object",
                    "site_id",
                    "from",
                    "to",
                    "summary",
                    "files",
                    "truncated"
                  ],
                  "description": "What changed between two versions of a site."
                },
                "example": {
                  "object": "site_diff",
                  "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "from": {
                    "id": "0b6c7e2a-1f3d-4e5a-9c8b-7d6e5f4a3b21",
                    "number": 3
                  },
                  "to": {
                    "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                    "number": 4
                  },
                  "summary": {
                    "added": 0,
                    "modified": 1,
                    "removed": 0,
                    "replaced": 0
                  },
                  "files": [
                    {
                      "path": "/index.html",
                      "status": "modified",
                      "kind": "text",
                      "old_bytes": 2310,
                      "new_bytes": 2342,
                      "diff": "--- a/index.html\n+++ b/index.html\n@@ -12,1 +12,1 @@\n-<p>Open 9 to 5</p>\n+<p>Open 8 to 6</p>\n"
                    }
                  ],
                  "truncated": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/rollback": {
      "post": {
        "operationId": "rollbackSite",
        "tags": [
          "Sites"
        ],
        "summary": "Roll a site back to an earlier version",
        "description": "Puts the files of an earlier version back live immediately. The rollback is itself a new version (cause rollback), so the history never loses anything and you can roll forward again. The same checks as for a deploy apply (abuse scan, plan limits, four-eyes approval). If the site already has exactly these files, nothing changes and unchanged is true: safe to retry. Send if_match_version to make sure nobody deployed in between. To restore only a few files, use POST /sites/{id}/restore-files.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "version_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The id of the version to go back to, from list_site_versions."
                  },
                  "number": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Instead of version_id: the number of the version."
                  },
                  "if_match_version": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional. The version of the site you are changing (version from get_site); if the site changed since, you get 409 version_conflict and nothing changes."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional. A short description for the version history, like \"Back to the old menu\"."
                  }
                },
                "additionalProperties": false,
                "description": "Which version to put back live: version_id or number."
              },
              "example": {
                "version_id": "0b6c7e2a-1f3d-4e5a-9c8b-7d6e5f4a3b21",
                "message": "Back to the old opening hours"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The site as it is now live, with the new version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "version": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "object": {
                          "type": "string",
                          "const": "site_version"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The id of the version: use it for get_site_diff, rollback_site, promote_site_version and label_site_version."
                        },
                        "number": {
                          "type": "integer",
                          "description": "The number of the version within the site: 1, 2, 3 and so on."
                        },
                        "site_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "created_at": {
                          "type": "string",
                          "description": "When this version was made (ISO 8601)."
                        },
                        "cause": {
                          "type": "string",
                          "enum": [
                            "baseline",
                            "deploy",
                            "partial_deploy",
                            "import",
                            "approved_deploy",
                            "rollback",
                            "promote",
                            "preview",
                            "file_restore"
                          ],
                          "description": "What made it: a deploy, a partial deploy (keep_existing), an import, a deploy after four-eyes approval, a rollback, a promoted preview, a preview, a few files restored from an older version (file_restore), or baseline (how the site was when its history started)."
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The message sent with the deploy, if any."
                        },
                        "author": {
                          "type": "object",
                          "properties": {
                            "user_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "via": {
                              "type": "string",
                              "enum": [
                                "portal",
                                "api_key",
                                "connected_app",
                                "system"
                              ]
                            },
                            "client": {
                              "type": "string",
                              "enum": [
                                "chatgpt",
                                "claude",
                                "api",
                                "portal",
                                "wizard",
                                "other"
                              ]
                            },
                            "client_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "user_id",
                            "via",
                            "client",
                            "client_name"
                          ],
                          "description": "Who made it, and through which client."
                        },
                        "changes": {
                          "type": "object",
                          "properties": {
                            "added": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "modified": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "removed": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "counts": {
                              "type": "object",
                              "properties": {
                                "added": {
                                  "type": "integer"
                                },
                                "modified": {
                                  "type": "integer"
                                },
                                "removed": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "added",
                                "modified",
                                "removed"
                              ]
                            },
                            "truncated": {
                              "type": "boolean",
                              "description": "true: the lists show the first 50 paths per kind; get_site_diff has all."
                            }
                          },
                          "required": [
                            "added",
                            "modified",
                            "removed",
                            "counts",
                            "truncated"
                          ],
                          "description": "The paths that changed compared to the version before."
                        },
                        "files": {
                          "type": "integer"
                        },
                        "bytes": {
                          "type": "integer"
                        },
                        "current": {
                          "type": "boolean",
                          "description": "true for the version that is live now."
                        },
                        "preview": {
                          "type": "boolean",
                          "description": "true for a preview that never went live."
                        },
                        "preview_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The secret address of a preview (not indexed). Share it only with people who should see it."
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "labeled_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "restored_from": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "For a rollback or promote: the version it came from."
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "When the retention of the plan removes it; null for the current version, labeled versions and checkpoints."
                        },
                        "auto_label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "before big change",
                            "stable",
                            "held by guardrail",
                            null
                          ],
                          "description": "A marker Hypamoon sets itself, apart from label: \"before big change\" is the version that was live when a deploy hit the guardrail (kept like a labeled version), \"stable\" a version that stayed live for at least 24 hours and was not rolled back (both good versions to go back to), and \"held by guardrail\" a preview the guardrail made instead of putting a deploy live."
                        }
                      },
                      "required": [
                        "object",
                        "id",
                        "number",
                        "site_id",
                        "created_at",
                        "cause",
                        "message",
                        "author",
                        "changes",
                        "files",
                        "bytes",
                        "current",
                        "preview",
                        "preview_url",
                        "label",
                        "labeled_at",
                        "restored_from",
                        "expires_at",
                        "auto_label"
                      ],
                      "description": "One version of a site: a snapshot of its files."
                    },
                    "unchanged": {
                      "type": "boolean",
                      "description": "true: the site already had exactly these files, so nothing changed (safe to retry)."
                    }
                  },
                  "required": [
                    "site",
                    "version",
                    "unchanged"
                  ],
                  "description": "The site after the rollback or promote."
                },
                "example": {
                  "site": {
                    "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "slug": "acme-plumbing",
                    "name": "Acme Plumbing",
                    "url": "https://acme-plumbing.hypamoon.app",
                    "files": [
                      {
                        "path": "/index.html",
                        "content_type": "text/html",
                        "bytes": 272,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      },
                      {
                        "path": "/style.css",
                        "content_type": "text/css",
                        "bytes": 98,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      }
                    ],
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "updated_at": "2026-10-01T09:00:00.000Z",
                    "version": "k3Q9vXbT2mLc8Rw1",
                    "password_protected": false,
                    "test": false,
                    "taken_offline": null
                  },
                  "version": {
                    "object": "site_version",
                    "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                    "number": 5,
                    "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "created_at": "2026-10-06T09:12:00Z",
                    "cause": "rollback",
                    "message": "New opening hours",
                    "author": {
                      "user_id": "5b1d6f3e-9a0c-4f2e-8d7b-2c4a1e9f0b33",
                      "via": "connected_app",
                      "client": "claude",
                      "client_name": "Claude"
                    },
                    "changes": {
                      "added": [],
                      "modified": [
                        "/index.html"
                      ],
                      "removed": [],
                      "counts": {
                        "added": 0,
                        "modified": 1,
                        "removed": 0
                      },
                      "truncated": false
                    },
                    "files": 3,
                    "bytes": 18240,
                    "current": true,
                    "preview": false,
                    "preview_url": null,
                    "label": null,
                    "labeled_at": null,
                    "restored_from": "0b6c7e2a-1f3d-4e5a-9c8b-7d6e5f4a3b21",
                    "expires_at": null,
                    "auto_label": null
                  },
                  "unchanged": false
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the rollback waits for another owner or builder. Nothing has changed yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_update",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/restore-files": {
      "post": {
        "operationId": "restoreSiteFiles",
        "tags": [
          "Sites"
        ],
        "summary": "Restore a few files from an earlier version",
        "description": "Puts only the files in paths back as they were in an earlier version, live immediately; every other file stays as it is now. Use it when one page, script or stylesheet broke and the rest of the site is fine. The result is a new version (cause file_restore) with a message, so it can be undone too. A path that was not in that version is a 422. The same checks as for a deploy apply (abuse scan, plan limits, four-eyes approval). If the files are already like that, nothing changes and unchanged is true.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "version_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The id of the version to take the files from, from list_site_versions."
                  },
                  "number": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Instead of version_id: the number of the version."
                  },
                  "paths": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 200
                    },
                    "minItems": 1,
                    "maxItems": 100,
                    "description": "The paths to restore, like [\"/app.js\", \"/style.css\"]. Every other file stays as it is now."
                  },
                  "if_match_version": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional. The version of the site you are changing (version from get_site); if the site changed since, you get 409 version_conflict and nothing changes."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional. A short description for the version history, like \"Back to the old menu\"."
                  }
                },
                "required": [
                  "paths"
                ],
                "additionalProperties": false,
                "description": "Which files to restore, and from which version."
              },
              "example": {
                "number": 4,
                "paths": [
                  "/app.js",
                  "/style.css"
                ],
                "message": "Back to the working app.js and style.css"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The site as it is now live, with the new version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "version": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "object": {
                          "type": "string",
                          "const": "site_version"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The id of the version: use it for get_site_diff, rollback_site, promote_site_version and label_site_version."
                        },
                        "number": {
                          "type": "integer",
                          "description": "The number of the version within the site: 1, 2, 3 and so on."
                        },
                        "site_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "created_at": {
                          "type": "string",
                          "description": "When this version was made (ISO 8601)."
                        },
                        "cause": {
                          "type": "string",
                          "enum": [
                            "baseline",
                            "deploy",
                            "partial_deploy",
                            "import",
                            "approved_deploy",
                            "rollback",
                            "promote",
                            "preview",
                            "file_restore"
                          ],
                          "description": "What made it: a deploy, a partial deploy (keep_existing), an import, a deploy after four-eyes approval, a rollback, a promoted preview, a preview, a few files restored from an older version (file_restore), or baseline (how the site was when its history started)."
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The message sent with the deploy, if any."
                        },
                        "author": {
                          "type": "object",
                          "properties": {
                            "user_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "via": {
                              "type": "string",
                              "enum": [
                                "portal",
                                "api_key",
                                "connected_app",
                                "system"
                              ]
                            },
                            "client": {
                              "type": "string",
                              "enum": [
                                "chatgpt",
                                "claude",
                                "api",
                                "portal",
                                "wizard",
                                "other"
                              ]
                            },
                            "client_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "user_id",
                            "via",
                            "client",
                            "client_name"
                          ],
                          "description": "Who made it, and through which client."
                        },
                        "changes": {
                          "type": "object",
                          "properties": {
                            "added": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "modified": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "removed": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "counts": {
                              "type": "object",
                              "properties": {
                                "added": {
                                  "type": "integer"
                                },
                                "modified": {
                                  "type": "integer"
                                },
                                "removed": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "added",
                                "modified",
                                "removed"
                              ]
                            },
                            "truncated": {
                              "type": "boolean",
                              "description": "true: the lists show the first 50 paths per kind; get_site_diff has all."
                            }
                          },
                          "required": [
                            "added",
                            "modified",
                            "removed",
                            "counts",
                            "truncated"
                          ],
                          "description": "The paths that changed compared to the version before."
                        },
                        "files": {
                          "type": "integer"
                        },
                        "bytes": {
                          "type": "integer"
                        },
                        "current": {
                          "type": "boolean",
                          "description": "true for the version that is live now."
                        },
                        "preview": {
                          "type": "boolean",
                          "description": "true for a preview that never went live."
                        },
                        "preview_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The secret address of a preview (not indexed). Share it only with people who should see it."
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "labeled_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "restored_from": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "For a rollback or promote: the version it came from."
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "When the retention of the plan removes it; null for the current version, labeled versions and checkpoints."
                        },
                        "auto_label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "before big change",
                            "stable",
                            "held by guardrail",
                            null
                          ],
                          "description": "A marker Hypamoon sets itself, apart from label: \"before big change\" is the version that was live when a deploy hit the guardrail (kept like a labeled version), \"stable\" a version that stayed live for at least 24 hours and was not rolled back (both good versions to go back to), and \"held by guardrail\" a preview the guardrail made instead of putting a deploy live."
                        }
                      },
                      "required": [
                        "object",
                        "id",
                        "number",
                        "site_id",
                        "created_at",
                        "cause",
                        "message",
                        "author",
                        "changes",
                        "files",
                        "bytes",
                        "current",
                        "preview",
                        "preview_url",
                        "label",
                        "labeled_at",
                        "restored_from",
                        "expires_at",
                        "auto_label"
                      ],
                      "description": "One version of a site: a snapshot of its files."
                    },
                    "unchanged": {
                      "type": "boolean",
                      "description": "true: the site already had exactly these files, so nothing changed (safe to retry)."
                    }
                  },
                  "required": [
                    "site",
                    "version",
                    "unchanged"
                  ],
                  "description": "The site after the rollback or promote."
                },
                "example": {
                  "site": {
                    "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "slug": "acme-plumbing",
                    "name": "Acme Plumbing",
                    "url": "https://acme-plumbing.hypamoon.app",
                    "files": [
                      {
                        "path": "/index.html",
                        "content_type": "text/html",
                        "bytes": 272,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      },
                      {
                        "path": "/style.css",
                        "content_type": "text/css",
                        "bytes": 98,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      }
                    ],
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "updated_at": "2026-10-01T09:00:00.000Z",
                    "version": "k3Q9vXbT2mLc8Rw1",
                    "password_protected": false,
                    "test": false,
                    "taken_offline": null
                  },
                  "version": {
                    "object": "site_version",
                    "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                    "number": 7,
                    "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "created_at": "2026-10-06T09:12:00Z",
                    "cause": "file_restore",
                    "message": "Back to the working app.js and style.css",
                    "author": {
                      "user_id": "5b1d6f3e-9a0c-4f2e-8d7b-2c4a1e9f0b33",
                      "via": "connected_app",
                      "client": "claude",
                      "client_name": "Claude"
                    },
                    "changes": {
                      "added": [],
                      "modified": [
                        "/index.html"
                      ],
                      "removed": [],
                      "counts": {
                        "added": 0,
                        "modified": 1,
                        "removed": 0
                      },
                      "truncated": false
                    },
                    "files": 3,
                    "bytes": 18240,
                    "current": true,
                    "preview": false,
                    "preview_url": null,
                    "label": null,
                    "labeled_at": null,
                    "restored_from": "0b6c7e2a-1f3d-4e5a-9c8b-7d6e5f4a3b21",
                    "expires_at": null,
                    "auto_label": null
                  },
                  "unchanged": false
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the restore waits for another owner or builder. Nothing has changed yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_update",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/versions/{versionId}/promote": {
      "post": {
        "operationId": "promoteSiteVersion",
        "tags": [
          "Sites"
        ],
        "summary": "Publish a preview",
        "description": "Publishes a preview (deploy_site with preview: true) as a new version (cause promote). The same checks as for a deploy apply, including four-eyes approval. Promoting twice changes nothing the second time.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "description": "The id of the version, from list_site_versions.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "if_match_version": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional. The version of the site you are changing (version from get_site); if the site changed since, you get 409 version_conflict and nothing changes."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional. A short description for the version history, like \"Back to the old menu\"."
                  }
                },
                "additionalProperties": false,
                "description": "Optional settings for putting the preview live."
              },
              "example": {
                "message": "Spring menu"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The site as it is now live, with the new version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "version": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "object": {
                          "type": "string",
                          "const": "site_version"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The id of the version: use it for get_site_diff, rollback_site, promote_site_version and label_site_version."
                        },
                        "number": {
                          "type": "integer",
                          "description": "The number of the version within the site: 1, 2, 3 and so on."
                        },
                        "site_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "created_at": {
                          "type": "string",
                          "description": "When this version was made (ISO 8601)."
                        },
                        "cause": {
                          "type": "string",
                          "enum": [
                            "baseline",
                            "deploy",
                            "partial_deploy",
                            "import",
                            "approved_deploy",
                            "rollback",
                            "promote",
                            "preview",
                            "file_restore"
                          ],
                          "description": "What made it: a deploy, a partial deploy (keep_existing), an import, a deploy after four-eyes approval, a rollback, a promoted preview, a preview, a few files restored from an older version (file_restore), or baseline (how the site was when its history started)."
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The message sent with the deploy, if any."
                        },
                        "author": {
                          "type": "object",
                          "properties": {
                            "user_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "via": {
                              "type": "string",
                              "enum": [
                                "portal",
                                "api_key",
                                "connected_app",
                                "system"
                              ]
                            },
                            "client": {
                              "type": "string",
                              "enum": [
                                "chatgpt",
                                "claude",
                                "api",
                                "portal",
                                "wizard",
                                "other"
                              ]
                            },
                            "client_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "user_id",
                            "via",
                            "client",
                            "client_name"
                          ],
                          "description": "Who made it, and through which client."
                        },
                        "changes": {
                          "type": "object",
                          "properties": {
                            "added": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "modified": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "removed": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "counts": {
                              "type": "object",
                              "properties": {
                                "added": {
                                  "type": "integer"
                                },
                                "modified": {
                                  "type": "integer"
                                },
                                "removed": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "added",
                                "modified",
                                "removed"
                              ]
                            },
                            "truncated": {
                              "type": "boolean",
                              "description": "true: the lists show the first 50 paths per kind; get_site_diff has all."
                            }
                          },
                          "required": [
                            "added",
                            "modified",
                            "removed",
                            "counts",
                            "truncated"
                          ],
                          "description": "The paths that changed compared to the version before."
                        },
                        "files": {
                          "type": "integer"
                        },
                        "bytes": {
                          "type": "integer"
                        },
                        "current": {
                          "type": "boolean",
                          "description": "true for the version that is live now."
                        },
                        "preview": {
                          "type": "boolean",
                          "description": "true for a preview that never went live."
                        },
                        "preview_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The secret address of a preview (not indexed). Share it only with people who should see it."
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "labeled_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "restored_from": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "For a rollback or promote: the version it came from."
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "When the retention of the plan removes it; null for the current version, labeled versions and checkpoints."
                        },
                        "auto_label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "before big change",
                            "stable",
                            "held by guardrail",
                            null
                          ],
                          "description": "A marker Hypamoon sets itself, apart from label: \"before big change\" is the version that was live when a deploy hit the guardrail (kept like a labeled version), \"stable\" a version that stayed live for at least 24 hours and was not rolled back (both good versions to go back to), and \"held by guardrail\" a preview the guardrail made instead of putting a deploy live."
                        }
                      },
                      "required": [
                        "object",
                        "id",
                        "number",
                        "site_id",
                        "created_at",
                        "cause",
                        "message",
                        "author",
                        "changes",
                        "files",
                        "bytes",
                        "current",
                        "preview",
                        "preview_url",
                        "label",
                        "labeled_at",
                        "restored_from",
                        "expires_at",
                        "auto_label"
                      ],
                      "description": "One version of a site: a snapshot of its files."
                    },
                    "unchanged": {
                      "type": "boolean",
                      "description": "true: the site already had exactly these files, so nothing changed (safe to retry)."
                    }
                  },
                  "required": [
                    "site",
                    "version",
                    "unchanged"
                  ],
                  "description": "The site after the rollback or promote."
                },
                "example": {
                  "site": {
                    "id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "slug": "acme-plumbing",
                    "name": "Acme Plumbing",
                    "url": "https://acme-plumbing.hypamoon.app",
                    "files": [
                      {
                        "path": "/index.html",
                        "content_type": "text/html",
                        "bytes": 272,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      },
                      {
                        "path": "/style.css",
                        "content_type": "text/css",
                        "bytes": 98,
                        "updated_at": "2026-10-01T09:00:00.000Z"
                      }
                    ],
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "updated_at": "2026-10-01T09:00:00.000Z",
                    "version": "k3Q9vXbT2mLc8Rw1",
                    "password_protected": false,
                    "test": false,
                    "taken_offline": null
                  },
                  "version": {
                    "object": "site_version",
                    "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                    "number": 6,
                    "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                    "created_at": "2026-10-06T09:12:00Z",
                    "cause": "promote",
                    "message": "New opening hours",
                    "author": {
                      "user_id": "5b1d6f3e-9a0c-4f2e-8d7b-2c4a1e9f0b33",
                      "via": "connected_app",
                      "client": "claude",
                      "client_name": "Claude"
                    },
                    "changes": {
                      "added": [],
                      "modified": [
                        "/index.html"
                      ],
                      "removed": [],
                      "counts": {
                        "added": 0,
                        "modified": 1,
                        "removed": 0
                      },
                      "truncated": false
                    },
                    "files": 3,
                    "bytes": 18240,
                    "current": true,
                    "preview": false,
                    "preview_url": null,
                    "label": null,
                    "labeled_at": null,
                    "restored_from": null,
                    "expires_at": null,
                    "auto_label": null
                  },
                  "unchanged": false
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: publishing the preview waits for another owner or builder.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "site_update",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/versions/{versionId}/label": {
      "put": {
        "operationId": "labelSiteVersion",
        "tags": [
          "Sites"
        ],
        "summary": "Label a version",
        "description": "Gives a version a name, like \"before launch\", unique per site. A labeled version is kept beyond the plan's retention period; the plan sets how many labels a site can have. label null removes it.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "description": "The id of the version, from list_site_versions.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "description": "A name for this version, like \"before launch\" (1 to 60 characters, unique per site). null or an empty string removes it."
                  }
                },
                "required": [
                  "label"
                ],
                "additionalProperties": false,
                "description": "The label of a version."
              },
              "example": {
                "label": "before launch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The version with its label.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "site_version"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the version: use it for get_site_diff, rollback_site, promote_site_version and label_site_version."
                    },
                    "number": {
                      "type": "integer",
                      "description": "The number of the version within the site: 1, 2, 3 and so on."
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "When this version was made (ISO 8601)."
                    },
                    "cause": {
                      "type": "string",
                      "enum": [
                        "baseline",
                        "deploy",
                        "partial_deploy",
                        "import",
                        "approved_deploy",
                        "rollback",
                        "promote",
                        "preview",
                        "file_restore"
                      ],
                      "description": "What made it: a deploy, a partial deploy (keep_existing), an import, a deploy after four-eyes approval, a rollback, a promoted preview, a preview, a few files restored from an older version (file_restore), or baseline (how the site was when its history started)."
                    },
                    "message": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The message sent with the deploy, if any."
                    },
                    "author": {
                      "type": "object",
                      "properties": {
                        "user_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "via": {
                          "type": "string",
                          "enum": [
                            "portal",
                            "api_key",
                            "connected_app",
                            "system"
                          ]
                        },
                        "client": {
                          "type": "string",
                          "enum": [
                            "chatgpt",
                            "claude",
                            "api",
                            "portal",
                            "wizard",
                            "other"
                          ]
                        },
                        "client_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "user_id",
                        "via",
                        "client",
                        "client_name"
                      ],
                      "description": "Who made it, and through which client."
                    },
                    "changes": {
                      "type": "object",
                      "properties": {
                        "added": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "modified": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "removed": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "added": {
                              "type": "integer"
                            },
                            "modified": {
                              "type": "integer"
                            },
                            "removed": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "added",
                            "modified",
                            "removed"
                          ]
                        },
                        "truncated": {
                          "type": "boolean",
                          "description": "true: the lists show the first 50 paths per kind; get_site_diff has all."
                        }
                      },
                      "required": [
                        "added",
                        "modified",
                        "removed",
                        "counts",
                        "truncated"
                      ],
                      "description": "The paths that changed compared to the version before."
                    },
                    "files": {
                      "type": "integer"
                    },
                    "bytes": {
                      "type": "integer"
                    },
                    "current": {
                      "type": "boolean",
                      "description": "true for the version that is live now."
                    },
                    "preview": {
                      "type": "boolean",
                      "description": "true for a preview that never went live."
                    },
                    "preview_url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The secret address of a preview (not indexed). Share it only with people who should see it."
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "labeled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "restored_from": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "For a rollback or promote: the version it came from."
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "When the retention of the plan removes it; null for the current version, labeled versions and checkpoints."
                    },
                    "auto_label": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "before big change",
                        "stable",
                        "held by guardrail",
                        null
                      ],
                      "description": "A marker Hypamoon sets itself, apart from label: \"before big change\" is the version that was live when a deploy hit the guardrail (kept like a labeled version), \"stable\" a version that stayed live for at least 24 hours and was not rolled back (both good versions to go back to), and \"held by guardrail\" a preview the guardrail made instead of putting a deploy live."
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "number",
                    "site_id",
                    "created_at",
                    "cause",
                    "message",
                    "author",
                    "changes",
                    "files",
                    "bytes",
                    "current",
                    "preview",
                    "preview_url",
                    "label",
                    "labeled_at",
                    "restored_from",
                    "expires_at",
                    "auto_label"
                  ],
                  "description": "One version of a site: a snapshot of its files."
                },
                "example": {
                  "object": "site_version",
                  "id": "8d0f5c1e-2b7a-4c55-9a51-3f1f2f6c9e10",
                  "number": 4,
                  "site_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
                  "created_at": "2026-10-06T09:12:00Z",
                  "cause": "partial_deploy",
                  "message": "New opening hours",
                  "author": {
                    "user_id": "5b1d6f3e-9a0c-4f2e-8d7b-2c4a1e9f0b33",
                    "via": "connected_app",
                    "client": "claude",
                    "client_name": "Claude"
                  },
                  "changes": {
                    "added": [],
                    "modified": [
                      "/index.html"
                    ],
                    "removed": [],
                    "counts": {
                      "added": 0,
                      "modified": 1,
                      "removed": 0
                    },
                    "truncated": false
                  },
                  "files": 3,
                  "bytes": 18240,
                  "current": true,
                  "preview": false,
                  "preview_url": null,
                  "label": "before launch",
                  "labeled_at": "2026-10-06T10:00:00Z",
                  "restored_from": null,
                  "expires_at": null,
                  "auto_label": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/store-readiness": {
      "get": {
        "operationId": "checkStoreReadiness",
        "tags": [
          "Sites"
        ],
        "summary": "Check whether a site is ready for the App Store and Google Play",
        "description": "Checks a site against what Apple (guideline 4.2: more than a website in a shell; 2.1: complete; 3.1.1: digital goods through in-app purchase) and Google Play expect: native value, a privacy policy URL, a support contact, an icon of the right size, complete content without placeholder pages, no external payment pages for digital goods, and no tracking without permission. Returns ready or not ready for each store, with concrete next steps, and what the user must arrange with Apple and Google themselves (their own developer account, a D-U-N-S number for a company, the 14-day closed test for a new personal Play account). Read-only; changes nothing.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict per store, every check and the next steps.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "app_url": {
                          "type": "string"
                        },
                        "test": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "app_url",
                        "test"
                      ]
                    },
                    "stores": {
                      "type": "object",
                      "properties": {
                        "apple": {
                          "type": "object",
                          "properties": {
                            "ready": {
                              "type": "boolean",
                              "description": "True when nothing blocks this store. The developer accounts are listed separately."
                            },
                            "blocking": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Ids of failed checks."
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Ids of checks with a warning."
                            },
                            "next_steps": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "ready",
                            "blocking",
                            "warnings",
                            "next_steps"
                          ]
                        },
                        "google_play": {
                          "type": "object",
                          "properties": {
                            "ready": {
                              "type": "boolean",
                              "description": "True when nothing blocks this store. The developer accounts are listed separately."
                            },
                            "blocking": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Ids of failed checks."
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Ids of checks with a warning."
                            },
                            "next_steps": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "ready",
                            "blocking",
                            "warnings",
                            "next_steps"
                          ]
                        }
                      },
                      "required": [
                        "apple",
                        "google_play"
                      ]
                    },
                    "checks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pass",
                              "warn",
                              "fail"
                            ]
                          },
                          "stores": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "apple",
                                "google_play"
                              ]
                            }
                          },
                          "detail": {
                            "type": "string"
                          },
                          "fix": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "title",
                          "status",
                          "stores",
                          "detail"
                        ]
                      }
                    },
                    "native_value": {
                      "type": "object",
                      "properties": {
                        "present": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "can_add": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "summary": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "present",
                        "can_add",
                        "summary"
                      ]
                    },
                    "accounts": {
                      "type": "object",
                      "properties": {
                        "apple": {
                          "type": "object",
                          "properties": {
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "fee": {
                              "type": "string"
                            },
                            "enroll_url": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "steps"
                          ]
                        },
                        "google_play": {
                          "type": "object",
                          "properties": {
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "fee": {
                              "type": "string"
                            },
                            "enroll_url": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "steps"
                          ]
                        }
                      },
                      "required": [
                        "apple",
                        "google_play"
                      ]
                    },
                    "unknown": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Parts of the project that could not be read; treat them carefully."
                    },
                    "next_steps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "site",
                    "stores",
                    "checks",
                    "native_value",
                    "accounts",
                    "unknown",
                    "next_steps"
                  ]
                },
                "example": {
                  "site": {
                    "id": "8d2c4b1e-3f5a-4c6d-9e7f-1a2b3c4d5e6f",
                    "name": "Acme Plumbing",
                    "app_url": "https://acme-plumbing.hypamoon.app",
                    "test": false
                  },
                  "stores": {
                    "apple": {
                      "ready": true,
                      "blocking": [],
                      "warnings": [
                        "native_value"
                      ],
                      "next_steps": [
                        "Strengthen it with: push notifications: the exported app registers each device with Hypamoon; sending notifications from the rails comes in phase 2; offline: the exported app shows its own offline screen; a service worker on the site makes pages work without a connection; sharing: a share button that calls navigator.share opens the phone's share sheet."
                      ]
                    },
                    "google_play": {
                      "ready": true,
                      "blocking": [],
                      "warnings": [],
                      "next_steps": []
                    }
                  },
                  "checks": [
                    {
                      "id": "site_online",
                      "title": "The site is online",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "The app loads https://acme-plumbing.hypamoon.app."
                    },
                    {
                      "id": "content_complete",
                      "title": "Content is complete",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "3 pages with real content."
                    },
                    {
                      "id": "privacy_policy",
                      "title": "Privacy policy URL",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "Found: /privacy.html. Make sure it also covers the app (push tokens, what the forms send)."
                    },
                    {
                      "id": "support_contact",
                      "title": "Support contact",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "Found contact page /contact.html, an email address, a phone number. Apple needs a support URL; Google Play needs a contact email."
                    },
                    {
                      "id": "app_icon",
                      "title": "App icon",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "/icon-1024.png is 1024x1024: good for every size."
                    },
                    {
                      "id": "native_value",
                      "title": "More than a website in a shell",
                      "status": "warn",
                      "stores": [
                        "apple"
                      ],
                      "detail": "Present: forms that send data (contact, booking or sign-up); live chat with the assistant. This helps with guideline 4.2, but Apple decides per review; push notifications and an offline screen make the case stronger.",
                      "fix": "Strengthen it with: push notifications: the exported app registers each device with Hypamoon; sending notifications from the rails comes in phase 2; offline: the exported app shows its own offline screen; a service worker on the site makes pages work without a connection; sharing: a share button that calls navigator.share opens the phone's share sheet."
                    },
                    {
                      "id": "native_value_play",
                      "title": "Useful beyond a website",
                      "status": "pass",
                      "stores": [
                        "google_play"
                      ],
                      "detail": "Present: forms that send data (contact, booking or sign-up); live chat with the assistant."
                    },
                    {
                      "id": "digital_payments",
                      "title": "Digital goods use the store's billing",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "No payment links or checkout found."
                    },
                    {
                      "id": "tracking",
                      "title": "No tracking without permission",
                      "status": "pass",
                      "stores": [
                        "apple",
                        "google_play"
                      ],
                      "detail": "No third-party trackers found."
                    }
                  ],
                  "native_value": {
                    "present": [
                      "forms that send data (contact, booking or sign-up)",
                      "live chat with the assistant"
                    ],
                    "can_add": [
                      "push notifications: the exported app registers each device with Hypamoon; sending notifications from the rails comes in phase 2",
                      "offline: the exported app shows its own offline screen; a service worker on the site makes pages work without a connection",
                      "sharing: a share button that calls navigator.share opens the phone's share sheet"
                    ],
                    "summary": "2 interactive parts. Apple judges guideline 4.2 per app; the more the app does that a browser does not, the better."
                  },
                  "accounts": {
                    "apple": {
                      "steps": [
                        "The app is published under the user's own Apple Developer account, never under Hypamoon's (guideline 4.2.6: apps from an app generator must be submitted by the owner of the content).",
                        "A company (Acme Plumbing B.V.) enrolls as an organization: it needs a D-U-N-S number (requested from Dun & Bradstreet, can take a few days), a legal entity name and a website on the company's domain. The company name is shown as the seller.",
                        "In App Store Connect the user accepts the agreements and, for paid apps, fills in tax and banking. Only the account holder can do this.",
                        "Later phases connect through an App Store Connect API key that the user adds in the Hypamoon portal (secret vault), never in this chat."
                      ],
                      "fee": "99 USD per year (Apple Developer Program)",
                      "enroll_url": "https://developer.apple.com/programs/enroll/"
                    },
                    "google_play": {
                      "steps": [
                        "The app is published under the user's own Google Play developer account. Google verifies the identity of the account holder.",
                        "An organization account (Acme Plumbing B.V.) needs a D-U-N-S number and an organization website; it can publish to production without the closed test.",
                        "Data safety and the content rating questionnaire are filled in by the user in the Play Console.",
                        "Later phases connect through a Google Play service account that the user adds in the Hypamoon portal (secret vault), never in this chat."
                      ],
                      "fee": "25 USD once (Google Play Console registration)",
                      "enroll_url": "https://play.google.com/console/signup"
                    }
                  },
                  "unknown": [],
                  "next_steps": [
                    "Strengthen it with: push notifications: the exported app registers each device with Hypamoon; sending notifications from the rails comes in phase 2; offline: the exported app shows its own offline screen; a service worker on the site makes pages work without a connection; sharing: a share button that calls navigator.share opens the phone's share sheet.",
                    "Ask the user to set up the developer accounts (see accounts); nobody can do that for them.",
                    "get_privacy_answers for App Privacy and Data safety.",
                    "generate_store_listing for the texts and the list of screenshots.",
                    "export_app_project for the app project."
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/store-listing": {
      "get": {
        "operationId": "generateStoreListing",
        "tags": [
          "Sites"
        ],
        "summary": "Draft the store listing for a site's app",
        "description": "A first draft of the App Store and Google Play listing from the site's own text, within each store's limits: name, subtitle, promotional text, description and keywords (Apple), title, short and full description (Google), a category, review notes, and which screenshots and graphics to make per store. In English; source_language says when the site is in another language and the texts need translating. No prices and no purchase links.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "app_name",
            "in": "query",
            "description": "The name of the app, if the user chose one. Leave it out to use the site's name.",
            "schema": {
              "type": "string",
              "maxLength": 30
            },
            "example": "Acme Plumbing"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft listing.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "app_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "app_url"
                      ]
                    },
                    "source_language": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    },
                    "apple": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "maxLength": 30
                        },
                        "subtitle": {
                          "type": "string",
                          "maxLength": 30
                        },
                        "promotional_text": {
                          "type": "string",
                          "maxLength": 170
                        },
                        "description": {
                          "type": "string",
                          "maxLength": 4000
                        },
                        "keywords": {
                          "type": "string",
                          "maxLength": 100
                        },
                        "primary_category": {
                          "type": "string"
                        },
                        "secondary_category": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "support_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "privacy_policy_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "name",
                        "subtitle",
                        "promotional_text",
                        "description",
                        "keywords",
                        "primary_category",
                        "secondary_category",
                        "support_url",
                        "privacy_policy_url"
                      ]
                    },
                    "google_play": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string",
                          "maxLength": 30
                        },
                        "short_description": {
                          "type": "string",
                          "maxLength": 80
                        },
                        "full_description": {
                          "type": "string",
                          "maxLength": 4000
                        },
                        "category": {
                          "type": "string"
                        },
                        "contact_email": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "privacy_policy_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "short_description",
                        "full_description",
                        "category",
                        "contact_email",
                        "privacy_policy_url"
                      ]
                    },
                    "category_reason": {
                      "type": "string"
                    },
                    "review_notes": {
                      "type": "string"
                    },
                    "screenshots": {
                      "type": "object",
                      "properties": {
                        "required": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "store": {
                                "type": "string",
                                "enum": [
                                  "apple",
                                  "google_play"
                                ]
                              },
                              "device": {
                                "type": "string"
                              },
                              "size": {
                                "type": "string"
                              },
                              "count": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "store",
                              "device",
                              "size",
                              "count",
                              "required"
                            ]
                          }
                        },
                        "graphics": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "store": {
                                "type": "string"
                              },
                              "what": {
                                "type": "string"
                              },
                              "size": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "store",
                              "what",
                              "size",
                              "required"
                            ]
                          }
                        },
                        "screens_to_capture": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "ipad_note": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "required",
                        "graphics",
                        "screens_to_capture",
                        "ipad_note"
                      ]
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "apple": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "number"
                            },
                            "subtitle": {
                              "type": "number"
                            },
                            "promotional_text": {
                              "type": "number"
                            },
                            "description": {
                              "type": "number"
                            },
                            "keywords": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "name",
                            "subtitle",
                            "promotional_text",
                            "description",
                            "keywords"
                          ]
                        },
                        "google_play": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "number"
                            },
                            "short_description": {
                              "type": "number"
                            },
                            "full_description": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "title",
                            "short_description",
                            "full_description"
                          ]
                        }
                      },
                      "required": [
                        "apple",
                        "google_play"
                      ]
                    }
                  },
                  "required": [
                    "site",
                    "source_language",
                    "note",
                    "apple",
                    "google_play",
                    "category_reason",
                    "review_notes",
                    "screenshots",
                    "limits"
                  ]
                },
                "example": {
                  "site": {
                    "id": "8d2c4b1e-3f5a-4c6d-9e7f-1a2b3c4d5e6f",
                    "app_url": "https://acme-plumbing.hypamoon.app"
                  },
                  "source_language": "en",
                  "note": "Drafts from the site's own text. Read them with the user and make them sound like the business; never promise features the app does not have.",
                  "apple": {
                    "name": "Acme Plumbing",
                    "subtitle": "Book a visit",
                    "promotional_text": "Fast, friendly plumbing in Rotterdam. Book a visit online or call us, day and night.",
                    "description": "Fast, friendly plumbing in Rotterdam. Book a visit online or call us, day and night. Leaking tap, blocked drain or a new boiler: our plumbers are on the road every day across Rotterdam and nearby towns. Send the form and we confirm a time within the hour, or call us for an emergency and we come the same day. Questions? Read our privacy policy, visit the contact page, mail hello@acme-plumbing.example or call +44 7700 900123. What you can do in the app: • Send a request or booking in a few taps • Chat with us and get an answer right away, day and night • Call us with one tap • Get notified when there is news (you choose whether you want notifications) • Clear message when you are offline, and back in one tap Acme Plumbing is the official app of Acme Plumbing B.V..",
                    "keywords": "call,day,visit,book,rotterdam,across,blocked,boiler,come,confirm,contact,drain,emergency,every",
                    "primary_category": "Business",
                    "secondary_category": "Productivity",
                    "support_url": "https://acme-plumbing.hypamoon.app/contact.html",
                    "privacy_policy_url": "https://acme-plumbing.hypamoon.app/privacy.html"
                  },
                  "google_play": {
                    "title": "Acme Plumbing",
                    "short_description": "Fast, friendly plumbing in Rotterdam. Book a visit online or call us, day and…",
                    "full_description": "Fast, friendly plumbing in Rotterdam. Book a visit online or call us, day and night. Leaking tap, blocked drain or a new boiler: our plumbers are on the road every day across Rotterdam and nearby towns. Send the form and we confirm a time within the hour, or call us for an emergency and we come the same day. Questions? Read our privacy policy, visit the contact page, mail hello@acme-plumbing.example or call +44 7700 900123. What you can do in the app: • Send a request or booking in a few taps • Chat with us and get an answer right away, day and night • Call us with one tap • Get notified when there is news (you choose whether you want notifications) • Clear message when you are offline, and back in one tap Acme Plumbing is the official app of Acme Plumbing B.V..",
                    "category": "Business",
                    "contact_email": "hello@acme-plumbing.example",
                    "privacy_policy_url": "https://acme-plumbing.hypamoon.app/privacy.html"
                  },
                  "category_reason": "Nothing specific found; Business fits most company apps. Change it if another category describes the app better.",
                  "review_notes": "Acme Plumbing is the app of the website https://acme-plumbing.hypamoon.app, published by its owner, Acme Plumbing B.V.. The content is the owner's own.\nNative features: the app asks permission for push notifications and registers the device for updates from the owner; it shows an offline screen with a retry button when there is no connection; forms that send data (contact, booking or sign-up); live chat with the assistant.\nNo account is needed; every feature is available without signing in.\nPhone links open the phone's own dialer; calls go to the owner's business number.\nThe chat is answered by the owner's assistant; it says it is a digital assistant.\nQuestions about the review: use the contact details in App Review Information.",
                  "screenshots": {
                    "required": [
                      {
                        "store": "apple",
                        "device": "iPhone 6.9-inch display",
                        "size": "1320 x 2868 or 1290 x 2796 pixels (portrait)",
                        "count": "3 to 10",
                        "required": true
                      },
                      {
                        "store": "apple",
                        "device": "iPad 13-inch display",
                        "size": "2064 x 2752 pixels (portrait)",
                        "count": "3 to 10",
                        "required": false
                      },
                      {
                        "store": "google_play",
                        "device": "Phone",
                        "size": "1080 x 1920 pixels or larger, 9:16 (each side 320 to 3840 pixels)",
                        "count": "2 to 8 (4 or more recommended)",
                        "required": true
                      },
                      {
                        "store": "google_play",
                        "device": "7-inch and 10-inch tablet",
                        "size": "1200 x 1920 and 1600 x 2560 pixels",
                        "count": "up to 8 each",
                        "required": false
                      }
                    ],
                    "graphics": [
                      {
                        "store": "apple",
                        "what": "App icon",
                        "size": "1024 x 1024 PNG without transparency (comes from the app project)",
                        "required": true
                      },
                      {
                        "store": "google_play",
                        "what": "App icon",
                        "size": "512 x 512 PNG, 32-bit",
                        "required": true
                      },
                      {
                        "store": "google_play",
                        "what": "Feature graphic",
                        "size": "1024 x 500 PNG or JPG",
                        "required": true
                      }
                    ],
                    "screens_to_capture": [
                      "The home screen (first page of the app)",
                      "Privacy (/privacy.html)",
                      "Contact (/contact.html)",
                      "A form filled in, just before sending",
                      "The chat open with a question and its answer",
                      "The notification permission prompt or a notification on the lock screen"
                    ],
                    "ipad_note": "iPad screenshots are only required when the app runs on iPad. The exported project explains how to build it for iPhone only (Xcode: Targets > App > General > Supported Destinations, remove iPad)."
                  },
                  "limits": {
                    "apple": {
                      "name": 30,
                      "subtitle": 30,
                      "promotional_text": 170,
                      "description": 4000,
                      "keywords": 100
                    },
                    "google_play": {
                      "title": 30,
                      "short_description": 80,
                      "full_description": 4000
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/privacy-answers": {
      "get": {
        "operationId": "getPrivacyAnswers",
        "tags": [
          "Sites"
        ],
        "summary": "Answers for Apple App Privacy and Google Play Data safety",
        "description": "Derives the answers for App Privacy (Apple) and Data safety (Google) from what the site and the project actually use on Hypamoon: forms and their fields, the chat widget, the bot check, payments and paid access, push registration, sign-in forms, location and third-party scripts on the site. Each answer has its reason and source. Cautious by design: when in doubt, a data type is declared as collected. Includes a click-by-click list for each console, because only the user can fill in both forms there.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The answers per store, with reasons and console steps.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "app_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "app_url"
                      ]
                    },
                    "sources": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The rails and scripts the answers come from: forms, chat_widget, bot_check, payments, paid_access, push, sign_in, location, third_party:<name>."
                    },
                    "apple": {
                      "type": "object",
                      "properties": {
                        "data_collected": {
                          "type": "boolean"
                        },
                        "used_for_tracking": {
                          "type": "boolean"
                        },
                        "data_types": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "category": {
                                "type": "string"
                              },
                              "type": {
                                "type": "string"
                              },
                              "linked_to_user": {
                                "type": "boolean"
                              },
                              "used_for_tracking": {
                                "type": "boolean"
                              },
                              "purposes": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "reason": {
                                "type": "string"
                              },
                              "source": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "category",
                              "type",
                              "linked_to_user",
                              "used_for_tracking",
                              "purposes",
                              "reason",
                              "source"
                            ]
                          }
                        },
                        "console_steps": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "data_collected",
                        "used_for_tracking",
                        "data_types",
                        "console_steps"
                      ]
                    },
                    "google_play": {
                      "type": "object",
                      "properties": {
                        "collects_or_shares": {
                          "type": "boolean"
                        },
                        "encrypted_in_transit": {
                          "type": "boolean"
                        },
                        "deletion": {
                          "type": "string"
                        },
                        "data_types": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "category": {
                                "type": "string"
                              },
                              "type": {
                                "type": "string"
                              },
                              "collected": {
                                "type": "boolean",
                                "const": true
                              },
                              "shared": {
                                "type": "boolean"
                              },
                              "processed_ephemerally": {
                                "type": "boolean"
                              },
                              "required": {
                                "type": "boolean"
                              },
                              "purposes": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "reason": {
                                "type": "string"
                              },
                              "source": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "category",
                              "type",
                              "collected",
                              "shared",
                              "processed_ephemerally",
                              "required",
                              "purposes",
                              "reason",
                              "source"
                            ]
                          }
                        },
                        "console_steps": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "collects_or_shares",
                        "encrypted_in_transit",
                        "deletion",
                        "data_types",
                        "console_steps"
                      ]
                    },
                    "not_collected": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "what": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "what",
                          "reason"
                        ]
                      }
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "site",
                    "sources",
                    "apple",
                    "google_play",
                    "not_collected",
                    "notes"
                  ]
                },
                "example": {
                  "site": {
                    "id": "8d2c4b1e-3f5a-4c6d-9e7f-1a2b3c4d5e6f",
                    "name": "Acme Plumbing",
                    "app_url": "https://acme-plumbing.hypamoon.app"
                  },
                  "sources": [
                    "forms",
                    "chat_widget",
                    "bot_check",
                    "push"
                  ],
                  "apple": {
                    "data_collected": true,
                    "used_for_tracking": false,
                    "data_types": [
                      {
                        "category": "Contact Info",
                        "type": "Name",
                        "linked_to_user": true,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "Contact Info",
                        "type": "Email Address",
                        "linked_to_user": true,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "Contact Info",
                        "type": "Phone Number",
                        "linked_to_user": true,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "User Content",
                        "type": "Other User Content",
                        "linked_to_user": true,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it. The text of chat messages.",
                        "source": "forms, chat_widget"
                      },
                      {
                        "category": "User Content",
                        "type": "Customer Support",
                        "linked_to_user": true,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "Chat messages go to the owner's assistant on Hypamoon and are stored as a conversation the owner can read. People can type their name or contact details in a message, so we treat it as linked.",
                        "source": "chat_widget"
                      },
                      {
                        "category": "Diagnostics",
                        "type": "Other Diagnostic Data",
                        "linked_to_user": false,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "Forms and the chat use a bot check against abuse; it reads signals from the browser for a moment and does not identify the person.",
                        "source": "bot_check"
                      },
                      {
                        "category": "Identifiers",
                        "type": "Device ID",
                        "linked_to_user": false,
                        "used_for_tracking": false,
                        "purposes": [
                          "App Functionality"
                        ],
                        "reason": "The app registers a push token with Hypamoon so the owner can send notifications. It is stored per site, without a name or account. Apple does not always call a push token a device id; we declare it to be safe.",
                        "source": "push"
                      }
                    ],
                    "console_steps": [
                      "Open App Store Connect, choose the app, then App Privacy in the sidebar.",
                      "Next to Privacy Policy, click Edit and paste the privacy policy URL of the site. Save.",
                      "Under Data Types, click Get Started (or Edit).",
                      "\"Do you or your third-party partners collect data from this app?\" Answer Yes, we collect data from this app.",
                      "Tick every data type below, then Save. App Store Connect then asks about each one in turn.",
                      "Contact Info > Name: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with Yes, and \"Is this data used for tracking?\" with No.",
                      "Contact Info > Email Address: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with Yes, and \"Is this data used for tracking?\" with No.",
                      "Contact Info > Phone Number: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with Yes, and \"Is this data used for tracking?\" with No.",
                      "User Content > Other User Content: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with Yes, and \"Is this data used for tracking?\" with No.",
                      "User Content > Customer Support: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with Yes, and \"Is this data used for tracking?\" with No.",
                      "Diagnostics > Other Diagnostic Data: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with No, and \"Is this data used for tracking?\" with No.",
                      "Identifiers > Device ID: tick it, choose the purposes App Functionality, answer \"Is this data linked to the user's identity?\" with No, and \"Is this data used for tracking?\" with No.",
                      "Click Publish at the top right. The labels go live with the next version you submit."
                    ]
                  },
                  "google_play": {
                    "collects_or_shares": true,
                    "encrypted_in_transit": true,
                    "deletion": "People can ask the owner to delete their data by email to hello@acme-plumbing.example. Google asks for a web link where people can request deletion; point it at the privacy policy or contact page.",
                    "data_types": [
                      {
                        "category": "Personal info",
                        "type": "Name",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": false,
                        "purposes": [
                          "App functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "Personal info",
                        "type": "Email address",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": false,
                        "purposes": [
                          "App functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "Personal info",
                        "type": "Phone number",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": false,
                        "purposes": [
                          "App functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "App activity",
                        "type": "Other user-generated content",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": false,
                        "purposes": [
                          "App functionality"
                        ],
                        "reason": "A form on the site sends this to the owner's data collection on Hypamoon (and by email to the owner); it is stored with the entry, so it is linked to the person who sent it.",
                        "source": "forms"
                      },
                      {
                        "category": "Messages",
                        "type": "Other in-app messages",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": false,
                        "purposes": [
                          "App functionality"
                        ],
                        "reason": "Chat messages are stored as a conversation the owner can read. An AI model answers them as a service provider for the owner, which Google does not count as sharing.",
                        "source": "chat_widget"
                      },
                      {
                        "category": "Device or other IDs",
                        "type": "Device or other IDs",
                        "collected": true,
                        "shared": false,
                        "processed_ephemerally": false,
                        "required": true,
                        "purposes": [
                          "Fraud prevention, security, and compliance",
                          "App functionality"
                        ],
                        "reason": "A bot check looks at the browser against abuse when someone sends a form or starts a chat; processed only for that check. The app registers a push token so the owner can send notifications; people can refuse notifications.",
                        "source": "bot_check, push"
                      }
                    ],
                    "console_steps": [
                      "Open the Play Console, choose the app, then Policy and programs > App content > Data safety, and click Start.",
                      "\"Does your app collect or share any of the required user data types?\" Answer Yes.",
                      "\"Is all of the user data collected by your app encrypted in transit?\" Answer Yes (every page and API call uses HTTPS).",
                      "\"Which of the following methods of account creation does your app support?\" Pick what the site really offers; if people cannot create an account, choose \"My app does not allow users to create an account\".",
                      "\"Do you provide a way for users to request that their data is deleted?\" Answer Yes, and give the URL of the privacy policy or contact page (people ask by email to hello@acme-plumbing.example).",
                      "Next. Tick each data type below, then Next again and answer the questions per type.",
                      "Personal info > Name: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Users can choose; purposes: App functionality.",
                      "Personal info > Email address: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Users can choose; purposes: App functionality.",
                      "Personal info > Phone number: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Users can choose; purposes: App functionality.",
                      "App activity > Other user-generated content: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Users can choose; purposes: App functionality.",
                      "Messages > Other in-app messages: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Users can choose; purposes: App functionality.",
                      "Device or other IDs > Device or other IDs: tick Collected; \"Is this data processed ephemerally?\" No; \"Is this data required?\" Required; purposes: Fraud prevention, security, and compliance, App functionality.",
                      "Check the preview, then Save and submit for review."
                    ]
                  },
                  "not_collected": [
                    {
                      "what": "Audio and call recordings",
                      "reason": "Calls start in the phone's own dialer through a tel: link, outside the app. If the app ever records audio itself, this changes."
                    },
                    {
                      "what": "Usage analytics by Hypamoon",
                      "reason": "The site host counts requests per site for capacity, without IP addresses, cookies or any identifier of the visitor."
                    },
                    {
                      "what": "Location",
                      "reason": "The site does not ask for the location."
                    },
                    {
                      "what": "Financial info",
                      "reason": "No checkout, payment links or paid access found."
                    }
                  ],
                  "notes": [
                    "Run this again after every change to the site: new forms, scripts or payments change the answers.",
                    "These answers are a careful starting point. The user is responsible for them and submits them in the console."
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/app-export": {
      "post": {
        "operationId": "exportAppProject",
        "tags": [
          "Sites"
        ],
        "summary": "Export a Capacitor app project for a site",
        "description": "Builds a Capacitor project that opens the site at its connected domain or hypamoon.app address, with the icon and splash from the site, a proposed app id in reverse domain form, an offline screen and push registration through Hypamoon, plus a README for building in Android Studio and Xcode. Returns a signed download link to a zip, valid for 15 minutes; a new export replaces the previous one. The zip contains no keys, tokens or passwords. Building and submitting in the cloud will come in a later phase.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "app_id": {
                    "type": "string",
                    "maxLength": 155,
                    "description": "Reverse domain form, like com.example.app. Leave it out for a proposal from the domain or the site's address. Cannot change after the first upload to a store."
                  },
                  "app_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 30,
                    "description": "The name under the icon. Leave it out to use the site's name."
                  },
                  "icon_path": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "Path of an image on the site to use as the icon, like /icon-1024.png (square PNG, 1024x1024). Leave it out to pick the best icon the site has."
                  },
                  "background_color": {
                    "type": "string",
                    "maxLength": 7,
                    "description": "Background of the icon and splash screen as a hex color, like #ffffff. Leave it out to use the site's theme color."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "app_id": "com.acmeplumbing.app",
                "icon_path": "/icon-1024.png"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The project is ready to download.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "download_url": {
                      "type": "string",
                      "description": "A signed link to the zip, valid for 15 minutes. Give it to the user; never store it."
                    },
                    "expires_at": {
                      "type": "string"
                    },
                    "app_id": {
                      "type": "string"
                    },
                    "app_id_reason": {
                      "type": "string"
                    },
                    "app_name": {
                      "type": "string"
                    },
                    "app_url": {
                      "type": "string",
                      "description": "The address the app opens: the connected domain, or the site's hypamoon.app address."
                    },
                    "icon": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Where the icon is in the zip, or null when the user must add one."
                    },
                    "background_color": {
                      "type": "string"
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "bytes": {
                      "type": "integer"
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "test": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "download_url",
                    "expires_at",
                    "app_id",
                    "app_id_reason",
                    "app_name",
                    "app_url",
                    "icon",
                    "background_color",
                    "files",
                    "bytes",
                    "warnings",
                    "test"
                  ]
                },
                "example": {
                  "download_url": "https://api.hypamoon.com/api/v1/public/app-exports/k7q2m4x9r3t6w8y2b5c7d9f3gh?o=5f0c9a2e-1b3d-4e5f-8a9b-0c1d2e3f4a5b&n=acme-plumbing-app&e=1791282000&s=3q2-7wEXAMPLEsignature",
                  "expires_at": "2026-10-06T12:20:00.000Z",
                  "app_id": "com.acmeplumbing.app",
                  "app_id_reason": "Chosen by the user.",
                  "app_name": "Acme Plumbing",
                  "app_url": "https://acme-plumbing.hypamoon.app",
                  "icon": "assets/icon-only.png",
                  "background_color": "#0b5cff",
                  "files": [
                    "assets/icon-only.png",
                    "package.json",
                    "capacitor.config.json",
                    "www/index.html",
                    "www/offline.html",
                    "www/app.js",
                    ".gitignore",
                    "hypamoon.json",
                    "README.md"
                  ],
                  "bytes": 9214,
                  "warnings": [],
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/advice": {
      "get": {
        "operationId": "getAdvice",
        "tags": [
          "Project"
        ],
        "summary": "Get the blueprint for a kind of application",
        "description": "Advice for building a webshop, appointments and bookings, a restaurant site, or a course or membership: the questions that matter (each with why and a default when the user does not know; only the ones marked blocking stop a working result), the build plan with the Hypamoon tools in order, the go-live checklist for the country (EU in detail, UK and US basics, a general fallback), the common mistakes, and a structured-data (JSON-LD) snippet for search engines. Advice only: nothing here is required of the user. The checklist is guidance, not legal or tax advice.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "The kind of application. Required, unless you ask for a topic. app: a build plan for an app with accounts, data per user and uploads, from description.",
            "schema": {
              "type": "string",
              "enum": [
                "webshop",
                "bookings",
                "restaurant",
                "course_membership",
                "wordpress_portal",
                "app"
              ]
            },
            "example": "webshop"
          },
          {
            "name": "description",
            "in": "query",
            "description": "Only with type app: what the app should do, in the user's words (one or two sentences).",
            "schema": {
              "type": "string",
              "maxLength": 500
            },
            "example": "A recipe book for my family with photos, voice memos and a share link"
          },
          {
            "name": "topic",
            "in": "query",
            "description": "Instead of type: one known problem (an upload blocked by a sandbox, an image too large, version_conflict, phone verification, a site nobody else can see, is Hypamoon down) with what to do and what to tell the user. Every blueprint also lists them in known_problems.",
            "schema": {
              "type": "string",
              "enum": [
                "sandbox_blocks_upload",
                "image_too_large",
                "version_conflict",
                "phone_verification_required",
                "site_not_visible",
                "is_hypamoon_down"
              ]
            },
            "example": "sandbox_blocks_upload"
          },
          {
            "name": "country",
            "in": "query",
            "description": "Where the business is: an ISO country code like NL, or a name. Leave it out for the Netherlands.",
            "schema": {
              "type": "string",
              "maxLength": 60
            },
            "example": "NL"
          },
          {
            "name": "audience",
            "in": "query",
            "description": "Who the user sells to; consumers (the default) adds the consumer rules.",
            "schema": {
              "type": "string",
              "enum": [
                "consumers",
                "businesses",
                "both"
              ]
            },
            "example": "consumers"
          }
        ],
        "responses": {
          "200": {
            "description": "The blueprint, with a short markdown summary. With topic: that one known problem (id, symptom, what_to_do, tell_user, other_topics).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Advice"
                },
                "example": {
                  "type": "webshop",
                  "title": "Webshop",
                  "summary": "A shop on the user's own site: products, a real checkout (or an honest order request while payments are not on), order emails and the pages a shop needs to go live.",
                  "country": "NL",
                  "region": "eu",
                  "audience": "consumers",
                  "how_to_use": "This is for you, the agent, to inform yourself; it is not a questionnaire for the user. Ask only the questions marked blocking (at most two or three), in plain words, and only if the user has not answered them already. For everything else use the default and mention briefly which defaults you used. If the user says \"just build it\" or skips a question, use the defaults and build. Follow build_plan with the real tools. Offer the go-live checklist afterwards as an option (\"Want me to go through what you might want to arrange before going live?\"), never as a condition for building or deploying. Name collections and their fields, and an app's tables and columns, in English and snake_case, descriptive (job_applications, contact_messages, full_name); the content and the texts on the site stay in the site's language.",
                  "ask_first": [
                    {
                      "id": "country",
                      "question": "Which country is your business in, and where do you sell to?",
                      "why": "It decides the rules (returns, VAT, what the site must show), the currency and the language.",
                      "default": "The Netherlands, selling within the Netherlands, in euro and Dutch.",
                      "blocking": true
                    },
                    {
                      "id": "products",
                      "question": "What do you sell, and roughly how many products? Do they come in variants like size or color?",
                      "why": "It decides how the product list is built and whether every variant needs its own price and stock.",
                      "default": "A small catalogue (up to 20 products) without variants; each variant would be its own product.",
                      "blocking": true
                    },
                    {
                      "id": "seller",
                      "question": "Do you sell as a business or as a private person, and to consumers or to other businesses?",
                      "why": "Consumer rules (14 days to withdraw, prices with VAT) apply when a business sells to consumers.",
                      "default": "A business selling to consumers.",
                      "blocking": false
                    },
                    {
                      "id": "shipping",
                      "question": "How do you ship, and what does shipping cost?",
                      "why": "Shipping costs must be clear before the customer orders.",
                      "default": "One flat shipping rate within the country, shown on every product page and before checkout; pickup on request.",
                      "blocking": false
                    },
                    {
                      "id": "returns",
                      "question": "How do you handle returns?",
                      "why": "Consumers in the EU and UK can usually withdraw within 14 days; the site has to explain how.",
                      "default": "The legal minimum: 14 days to withdraw, the customer pays return shipping, refund within 14 days.",
                      "blocking": false
                    },
                    {
                      "id": "stock",
                      "question": "Do you want the shop to track stock?",
                      "why": "With stock tracking a product goes off sale when it runs out.",
                      "default": "No stock tracking (unlimited); the owner takes a product off sale by hand.",
                      "blocking": false
                    },
                    {
                      "id": "payment_methods",
                      "question": "Which payment methods do you want to offer?",
                      "why": "Buyers expect their usual method (iDEAL in the Netherlands, cards elsewhere).",
                      "default": "What the user's own payment account offers (in the Netherlands usually iDEAL and cards).",
                      "blocking": false
                    },
                    {
                      "id": "order_emails",
                      "question": "Who should receive the order emails?",
                      "why": "Someone has to see every order and ship it.",
                      "default": "The project's billing email address.",
                      "blocking": false
                    },
                    {
                      "id": "language_currency",
                      "question": "Which language and currency should the shop use?",
                      "why": "Prices and texts must match where the customers are.",
                      "default": "The language and currency of the user's country.",
                      "blocking": false
                    }
                  ],
                  "build_plan": [
                    {
                      "id": "pages",
                      "title": "Pages first",
                      "how": "deploy_site with a home page, a product list, a product page per product (or one page with all products), /contact with the company details, /terms, /privacy, /returns with the withdrawal form, and /404.html. Use the user's real texts and prices; leave out what they do not have yet.",
                      "tools": [
                        "deploy_site"
                      ]
                    },
                    {
                      "id": "products",
                      "title": "Products as data",
                      "how": "With payments available: create_product_collection, then insert_document per product (name, price_cents including VAT, optional stock). Without payments: create_collection \"products\" with access {\"public_read\": true} and insert_document per product, so the site and the assistant read the same list.",
                      "tools": [
                        "create_product_collection",
                        "create_collection",
                        "insert_document"
                      ]
                    },
                    {
                      "id": "checkout",
                      "title": "A real checkout, or an honest order request",
                      "how": "Call get_payments_status: when ready is true, get_checkout_snippet per product and put the buy button on the product page. When the payment account is not connected yet, connect_payments gives the owner the link. When payments are not available (no such tool, or the user does not want them yet): build an order request instead. create_collection \"order_requests\" with access {\"public_write\": true} and triggers {\"email_on_insert\": true}, get_form_snippet, and a button that says \"Send order request\". The page says plainly that the shop confirms availability and sends payment details, and that nothing is paid yet. Never a \"Pay now\" button that does not take a payment.",
                      "tools": [
                        "get_payments_status",
                        "connect_payments",
                        "get_checkout_snippet",
                        "create_collection",
                        "get_form_snippet"
                      ]
                    },
                    {
                      "id": "orders",
                      "title": "Orders and emails",
                      "how": "Paid orders: list_orders and get_order, update_order_status to shipped. Order requests: query_documents on order_requests. With the Mail module on, send the buyer a confirmation with send_email (transactional) from the user's own domain (add_email_domain, verify_email_domain); otherwise tell the user to send it themselves.",
                      "tools": [
                        "list_orders",
                        "get_order",
                        "update_order_status",
                        "query_documents",
                        "send_email",
                        "add_email_domain",
                        "verify_email_domain"
                      ]
                    },
                    {
                      "id": "contact_form",
                      "title": "Contact form that really arrives",
                      "how": "create_collection \"contact\" with access {\"public_write\": true} and triggers {\"email_on_insert\": true}, then get_form_snippet on /contact.",
                      "tools": [
                        "create_collection",
                        "get_form_snippet"
                      ]
                    },
                    {
                      "id": "accounts",
                      "title": "Customer accounts (optional)",
                      "how": "Only if the user wants customers to log in and see their orders: deploy_app with auth, and set_table_owner_column so each customer sees only their own rows. A shop works fine without accounts.",
                      "tools": [
                        "deploy_app",
                        "set_table_owner_column"
                      ],
                      "optional": true
                    },
                    {
                      "id": "chat",
                      "title": "Chat that knows the shop (optional)",
                      "how": "create_collection \"faq\" with access {\"assistant_read\": true}, insert_document with shipping, returns and opening hours, then create_chat_widget with the site's origin and put the embed code on the site.",
                      "tools": [
                        "create_collection",
                        "insert_document",
                        "create_chat_widget"
                      ],
                      "optional": true
                    },
                    {
                      "id": "structured_data",
                      "title": "Structured data for search",
                      "how": "Add the Product block from structured_data.snippet to the head of each product page (one Product block per product, with that product's own name, price and image) with deploy_site and keep_existing: true. Replace every YOUR ... placeholder with the user's real details from the answers so far; never invent an address, phone number, price or opening hours, and leave out optional fields you do not know. check_readiness flags blocks that are incomplete or still have placeholders.",
                      "tools": [
                        "deploy_site"
                      ],
                      "optional": true
                    },
                    {
                      "id": "launch",
                      "title": "Before launch",
                      "how": "check_readiness for the site, fix what you can, and label_site_version \"before launch\" so this state is kept. Then tell the user in plain words what works and what does not yet.",
                      "tools": [
                        "check_readiness",
                        "label_site_version"
                      ]
                    }
                  ],
                  "go_live_checklist": [
                    {
                      "id": "company_details",
                      "level": "usually_required",
                      "description": "Company details on the site, easy to find (footer or contact page): the legal name, the geographic address, an email address and a phone number, the chamber of commerce or company registration number, and the VAT number if the business has one. In the Netherlands: the KvK number and the btw-id.",
                      "why": "EU and UK rules for online services and distance selling ask businesses to say who they are and how to reach them. Customers also trust a shop more when they see a real business behind it.",
                      "how_on_hypamoon": "Ask the user for these details (never invent them), put them on /contact and in the footer of every page with deploy_site.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "privacy_statement",
                      "level": "usually_required",
                      "description": "A privacy statement that says which personal data the site collects (forms, orders, bookings, chat), why, how long it is kept, who processes it, and how people can see or delete their data.",
                      "why": "The GDPR (EU) and UK GDPR require this as soon as you collect personal data; in the US, California (CalOPPA) requires a posted privacy policy for sites that collect personal information from California residents.",
                      "how_on_hypamoon": "Write it with the user from what the site really collects, put it on /privacy with deploy_site and link it in the footer and next to every form.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "cookie_notice",
                      "level": "recommended",
                      "description": "A cookie notice with a real choice, but only if the site uses non-essential cookies or trackers (analytics with personal data, advertising pixels, embedded videos that set cookies). A site with only functional storage needs no banner.",
                      "why": "The ePrivacy rules (EU) and PECR (UK) require consent before non-essential cookies are set. A banner that is not needed only gets in the way.",
                      "how_on_hypamoon": "Hypamoon sites, forms, checkout and the chat widget need no cookie banner by themselves. If the user adds a tracker, add a consent banner that loads it only after a yes.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "accessibility",
                      "level": "recommended",
                      "description": "Basic accessibility: readable contrast, alt text on images, labels on form fields, keyboard navigation, and an accessibility statement for online shops and services.",
                      "why": "The European Accessibility Act applies to e-commerce and many consumer services since 28 June 2025; micro-enterprises (fewer than 10 staff and at most 2 million euro turnover) are usually exempt for services. Accessible sites also sell better.",
                      "how_on_hypamoon": "Build the pages with labels, alt text and enough contrast; add /accessibility if the business is not a micro-enterprise.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "prices_incl_vat",
                      "level": "usually_required",
                      "description": "Prices for consumers shown as the total price, VAT included, with the currency (for example € 19,95).",
                      "why": "Consumer law in the EU and UK requires the total price including taxes; a price without currency or VAT confuses buyers.",
                      "how_on_hypamoon": "Show prices with the currency on the page; with checkout, price_cents in the product collection is the price including VAT.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "terms_and_conditions",
                      "level": "recommended",
                      "description": "Terms and conditions on /terms, linked in the footer and in the order flow, that the customer can read and save before ordering.",
                      "why": "Not every country requires them, but if the shop uses them, customers must be able to read them before they order; they also set delivery, payment and liability.",
                      "how_on_hypamoon": "Write them with the user, put them on /terms with deploy_site and link them next to the order button.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "withdrawal_right",
                      "level": "usually_required",
                      "description": "Information about the 14-day right of withdrawal (cancellation): how to withdraw, who pays return shipping, the refund within 14 days, the exceptions (for example made-to-order or sealed hygiene products), and the model withdrawal form.",
                      "why": "The EU Consumer Rights Directive and the UK Consumer Contracts Regulations give consumers 14 days to cancel most online purchases; without the information the period can be extended by up to 12 months.",
                      "how_on_hypamoon": "Put it on /returns with the model withdrawal form, link it in the footer and from the order flow (deploy_site).",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "withdrawal_function",
                      "level": "usually_required",
                      "description": "An easy way to withdraw online: a clearly labelled withdrawal function (like \"withdraw from contract here\") where the customer confirms their withdrawal, followed by a confirmation.",
                      "why": "An EU amendment to the Consumer Rights Directive (Directive 2023/2673) asks for an online withdrawal function for contracts made online, applying from 19 June 2026.",
                      "how_on_hypamoon": "A form on /returns that saves the withdrawal in a collection (create_collection with public_write and email_on_insert, get_form_snippet) and shows a confirmation.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "shipping_costs_upfront",
                      "level": "usually_required",
                      "description": "Shipping costs, delivery restrictions (countries) and the expected delivery time shown before the customer starts ordering.",
                      "why": "In the EU and UK this information must be clear at the start of the ordering process; everywhere, hidden costs at the end are the main reason people abandon a purchase.",
                      "how_on_hypamoon": "Show shipping costs on the product pages and next to the order button (deploy_site).",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "payment_methods_upfront",
                      "level": "usually_required",
                      "description": "The accepted payment methods shown at the latest at the start of the ordering process.",
                      "why": "The Consumer Rights Directive asks for this at the start of the order.",
                      "how_on_hypamoon": "List the methods of the user's payment account on the product page or in the footer.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "order_button_text",
                      "level": "usually_required",
                      "description": "The final order button says clearly that ordering means paying, like \"Order with obligation to pay\" or in Dutch \"Bestellen en betalen\"; not just \"Next\" or \"Continue\".",
                      "why": "The EU Consumer Rights Directive and the UK rules require this; without it the customer may not be bound by the order.",
                      "how_on_hypamoon": "Set the button text on the checkout button (get_checkout_snippet html) or the order request button.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "order_confirmation",
                      "level": "usually_required",
                      "description": "An order confirmation by email (a durable medium) with what was ordered, the price, the company details and the withdrawal information.",
                      "why": "EU and UK consumer law ask for a confirmation on a durable medium at the latest at delivery; customers everywhere expect one right away.",
                      "how_on_hypamoon": "Check what the checkout or the payment provider already sends; add one with send_email (Mail module) if needed, or tell the user to send it.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "legal_guarantee",
                      "level": "recommended",
                      "description": "A line about the legal guarantee: products must be as promised, and consumers keep their legal rights when something is wrong.",
                      "why": "The legal guarantee applies anyway (at least two years in most EU countries; in the Netherlands what the buyer may reasonably expect); saying so avoids disputes.",
                      "how_on_hypamoon": "Add it to /terms or /returns with deploy_site.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "complaints",
                      "level": "recommended",
                      "description": "How to complain, with an email address and a reasonable answer time, and the dispute body if the business is a member of one (in the Netherlands for example the Geschillencommissie or a trade association).",
                      "why": "The EU ODR platform closed on 20 July 2025 and the link is no longer needed, but a clear complaint route is still expected, and members of a dispute body must mention it.",
                      "how_on_hypamoon": "Add it to /terms or /contact with deploy_site.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "age_check",
                      "level": "usually_required",
                      "description": "An age check when selling age-restricted products (alcohol, tobacco and vapes, knives, some games): ask for confirmation online and check the ID at delivery.",
                      "why": "Selling these to minors is forbidden; in the Netherlands the Alcoholwet requires an age check at the door for alcohol delivered to the home.",
                      "how_on_hypamoon": "Only if relevant: add an age statement to the order form and tell the user to arrange the check at delivery.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "vat_registration",
                      "level": "recommended",
                      "description": "VAT set up correctly: the right rate in the prices, and the EU One Stop Shop (OSS) when sales to consumers in other EU countries pass 10,000 euro a year.",
                      "why": "Above that threshold the VAT of the buyer's country applies.",
                      "how_on_hypamoon": "Nothing to build; mention it to the user so they check it with their accountant.",
                      "checked_by_check_readiness": false
                    },
                    {
                      "id": "checkout_real",
                      "level": "usually_required",
                      "description": "Every buy, order or add-to-cart button leads to a real checkout or a real order request; a cart always ends in one.",
                      "why": "A button that does nothing loses the sale and makes the shop look fake.",
                      "how_on_hypamoon": "get_checkout_snippet when payments are ready, otherwise an order request form (create_collection, get_form_snippet).",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "structured_data",
                      "level": "recommended",
                      "description": "Structured data (JSON-LD from schema.org) that tells search engines what the business or product is: name, address, phone, opening hours, prices.",
                      "why": "Search engines can then show richer results, like opening hours, a price or a location on the map, instead of a plain link.",
                      "how_on_hypamoon": "Add the block from get_advice (structured_data.snippet) with the user's real details, with deploy_site and keep_existing: true.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "forms_saved",
                      "level": "usually_required",
                      "description": "Every form really saves what a visitor sends (in a collection or through an app), and the owner sees or gets each entry.",
                      "why": "A form that goes nowhere loses customers without anyone noticing. This is not a law; it is what makes the site work.",
                      "how_on_hypamoon": "create_collection with public_write (and email_on_insert so the owner gets each entry), then get_form_snippet and deploy_site.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "no_placeholders",
                      "level": "usually_required",
                      "description": "No lorem ipsum, example.com, \"your text here\" or other placeholder text, and no invented reviews, customers or numbers presented as real.",
                      "why": "Placeholders look unfinished; invented reviews or customers are misleading and, for consumers, an unfair commercial practice in the EU and UK.",
                      "how_on_hypamoon": "Replace placeholders with the user's real text, or leave the section out until they have it (deploy_site with keep_existing).",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "no_test_wording",
                      "level": "usually_required",
                      "description": "No test wording on the live site, like \"this is a test form\" or \"test environment\", once it is meant for real visitors.",
                      "why": "Visitors stop trusting a form that says it is a test, and they will not send real requests.",
                      "how_on_hypamoon": "Change the texts (and data-hypamoon-success on forms) with deploy_site, and make sure the form uses the live public token.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "internal_links",
                      "level": "recommended",
                      "description": "Every link inside the site leads to a page that exists, and buttons go somewhere real.",
                      "why": "Broken links and dead buttons make a site look like a mockup.",
                      "how_on_hypamoon": "Add the missing pages or fix the links with deploy_site.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "not_found_page",
                      "level": "recommended",
                      "description": "A friendly not-found page (/404.html) with a link back home and the contact details.",
                      "why": "Old links and typos end there; a plain error page loses the visitor.",
                      "how_on_hypamoon": "Add /404.html with deploy_site.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "no_fake_login",
                      "level": "usually_required",
                      "description": "No login that checks a password in JavaScript, and no member area that only hides content in the browser.",
                      "why": "Anyone can read the password in the page source; it protects nothing and gives the owner a false sense of safety.",
                      "how_on_hypamoon": "One shared password: password on deploy_site. Accounts per person: deploy_app with auth. Paid access: create_access_product and set_site_access.",
                      "checked_by_check_readiness": true
                    },
                    {
                      "id": "no_local_storage_db",
                      "level": "usually_required",
                      "description": "Orders, bookings, sign-ups and messages are not kept in the visitor's browser (localStorage) as if it were a database.",
                      "why": "localStorage stays on the visitor's own device: the owner never sees it, and it is gone when they clear their browser.",
                      "how_on_hypamoon": "Save it in a collection (create_collection, get_form_snippet) or through an app (deploy_app). localStorage is fine for a theme or language choice.",
                      "checked_by_check_readiness": true
                    }
                  ],
                  "pitfalls": [
                    {
                      "id": "fake_checkout",
                      "pitfall": "A \"Pay now\" or \"Add to cart\" button that does nothing, or a cart that only lives in the browser.",
                      "instead": "The real checkout when payments are ready, otherwise an honest order request that says payment follows later."
                    },
                    {
                      "id": "price_in_page",
                      "pitfall": "Trusting a price that the page sends to the server.",
                      "instead": "The checkout always reads price_cents from the product collection; show the price as text on the page."
                    },
                    {
                      "id": "no_shipping_costs",
                      "pitfall": "Shipping costs that only appear at the end.",
                      "instead": "Show shipping costs on the product page and before the order button."
                    },
                    {
                      "id": "no_withdrawal",
                      "pitfall": "No returns page or withdrawal form for consumers.",
                      "instead": "A /returns page with the 14-day rules and the model form, linked in the footer."
                    },
                    {
                      "id": "invented_reviews",
                      "pitfall": "Invented reviews, star ratings or \"1,000 happy customers\".",
                      "instead": "Only real reviews from the user, or none."
                    },
                    {
                      "id": "variants_as_one",
                      "pitfall": "Variants with different prices in one product.",
                      "instead": "Each variant with its own price as its own product."
                    },
                    {
                      "id": "forgot_order_email",
                      "pitfall": "Orders that nobody sees.",
                      "instead": "email_on_insert on order requests, list_orders for paid orders, and a clear owner email."
                    },
                    {
                      "id": "users_round_trip",
                      "pitfall": "App code that fetches /app-users itself on every page to show the people of the app.",
                      "instead": "Read users via env.HYPAMOON.users: env.HYPAMOON.users.list() or env.HYPAMOON.users.get(id) in the app's server code, usually answered next to the app without a round trip."
                    }
                  ],
                  "structured_data": {
                    "schema_type": "Product",
                    "why": "Search engines can show the product with its price and whether it is in stock. Use one block per product page, with that product's own details.",
                    "snippet": "<script type=\"application/ld+json\">\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"Product\",\n  \"name\": \"YOUR PRODUCT NAME\",\n  \"description\": \"YOUR PRODUCT DESCRIPTION\",\n  \"image\": \"YOUR PRODUCT IMAGE URL (absolute, https)\",\n  \"offers\": {\n    \"@type\": \"Offer\",\n    \"price\": \"YOUR PRICE (like 19.95)\",\n    \"priceCurrency\": \"YOUR CURRENCY CODE (like EUR)\",\n    \"availability\": \"https://schema.org/InStock\",\n    \"url\": \"YOUR PRODUCT PAGE URL\"\n  }\n}\n</script>",
                    "required": [
                      "name",
                      "offers.price",
                      "offers.priceCurrency"
                    ]
                  },
                  "note": "This is guidance to help the user prepare, not legal or tax advice. Rules differ per country and change over time; for anything that matters, the user checks with an advisor or the official sources (in the Netherlands for example ACM ConsuWijzer for consumer law, the Belastingdienst for VAT and the Autoriteit Persoonsgegevens for privacy).",
                  "other_types": {
                    "available": [
                      "webshop",
                      "bookings",
                      "restaurant",
                      "course_membership",
                      "wordpress_portal"
                    ],
                    "planned": [
                      "events",
                      "portfolio",
                      "internal_tool",
                      "saas"
                    ]
                  },
                  "known_problems": [
                    {
                      "id": "sandbox_blocks_upload",
                      "symptom": "The PUT to an upload_url fails or is blocked from your own environment (a 403, a network error, \"host not allowed\"), or deploy_site says upload_not_received.",
                      "what_to_do": "This is your sandbox, not Hypamoon: do not say Hypamoon is down or blocked. Send images under ~3 MB as base64 in deploy_site content instead. Files that are already online go as source_url in deploy_site (up to 100 per call). For larger or many local files, create_drop_link, give the user the url, and after they are done call get_drop and deploy its deploy_files; the user can also add them at https://app.hypamoon.com/media, after which deploy_site finds them by path, sha256 and bytes.",
                      "tell_user": "Your chat app blocked the upload, not Hypamoon. I will send the file another way, or give you a link to upload it yourself in your browser."
                    },
                    {
                      "id": "image_too_large",
                      "symptom": "site_too_large, or one request over 4 MB because of an image.",
                      "what_to_do": "Make a lighter copy first: at most about 2000 px wide, JPEG or WebP at quality ~80; that is usually under 500 KB and looks the same on screen. A file that must stay large goes through create_upload_urls, or create_drop_link if your environment cannot upload. The limits per file are in the error and in limits of create_upload_urls.",
                      "tell_user": "That image is bigger than a website needs. I will make a lighter copy that looks the same on screen."
                    },
                    {
                      "id": "version_conflict",
                      "symptom": "version_conflict (409 or 412) on deploy_site or set_call_plan.",
                      "what_to_do": "Something changed since you read it (another chat, a team member, the portal). Read it again (get_site or get_call_plan), apply your change on top of the current version, and send it with if_match_version set to current_version from the error. Never retry without reading, and never overwrite blindly.",
                      "tell_user": "The site changed in the meantime, so I did not overwrite it. I will apply your change to the latest version."
                    },
                    {
                      "id": "phone_verification_required",
                      "symptom": "phone_verification_required (403) when making a site public or connecting a domain.",
                      "what_to_do": "The account owner verifies their phone once at the verify_url in the error (https://app.hypamoon.com/verify-phone); a payment method also counts. Until then the site works with a password or as unlisted. Do not try to work around it.",
                      "tell_user": "Before the site can be public, please confirm your phone number once at the link I give you. It takes about a minute."
                    },
                    {
                      "id": "site_not_visible",
                      "symptom": "The user says the site works for them but not for others, or it does not show up in Google.",
                      "what_to_do": "Check get_site: an unlisted site only opens with its secret link, and a site with a password asks every visitor for it. update_site_address makes it public (that may need phone verification); rotate_site_secret_url makes a new secret link. A new public site takes days to appear in Google; check_readiness covers findability.",
                      "tell_user": "Your site is live, but it is set to private, so only people with the secret link can open it. I can make it public if you want."
                    },
                    {
                      "id": "is_hypamoon_down",
                      "symptom": "A step failed and it looks like Hypamoon might be down.",
                      "what_to_do": "Call get_status before you say anything about an outage. Operational: the problem is in the failing step or in your own environment; read its user_message and fix that step. A reported problem: tell the user what is affected and link https://status.hypamoon.com; retry later instead of inventing a workaround.",
                      "tell_user": "Hypamoon is working normally. The problem is likely in your chat app. (Use the user_message from get_status; it says so when there is a real problem.)"
                    }
                  ],
                  "summary_markdown": "## Webshop (EU, NL, selling to consumers)\nA shop on the user's own site: products, a real checkout (or an honest order request while payments are not on), order emails and the pages a shop needs to go live.\n\n**Ask only these, if not answered yet:** Which country is your business in, and where do you sell to? What do you sell, and roughly how many products? Do they come in variants like size or color?\n**Defaults for the rest:** seller: A business selling to consumers.; shipping: One flat shipping rate within the country, shown on every product page and before checkout; pickup on request.; returns: The legal minimum: 14 days to withdraw, the customer pays return shipping, refund within 14 days.; stock: No stock tracking (unlimited); the owner takes a product off sale by hand.; payment_methods: What the user's own payment account offers (in the Netherlands usually iDEAL and cards).; order_emails: The project's billing email address.; language_currency: The language and currency of the user's country.\n\n**Build plan:**\n1. Pages first: deploy_site\n2. Products as data: create_product_collection, create_collection, insert_document\n3. A real checkout, or an honest order request: get_payments_status, connect_payments, get_checkout_snippet, create_collection, get_form_snippet\n4. Orders and emails: list_orders, get_order, update_order_status, query_documents, send_email, add_email_domain, verify_email_domain\n5. Contact form that really arrives: create_collection, get_form_snippet\n6. Customer accounts (optional) (optional): deploy_app, set_table_owner_column\n7. Chat that knows the shop (optional) (optional): create_collection, insert_document, create_chat_widget\n8. Structured data for search (optional): deploy_site\n9. Before launch: check_readiness, label_site_version\n\n**Before going live (offer it, do not impose it):** 16 usually required and 9 recommended points; check_readiness verifies 14 of them on the site.\n\n**Common mistakes:** A \"Pay now\" or \"Add to cart\" button that does nothing, or a cart that only lives in the browser. Trusting a price that the page sends to the server. Shipping costs that only appear at the end. No returns page or withdrawal form for consumers. Invented reviews, star ratings or \"1,000 happy customers\". Variants with different prices in one product. Orders that nobody sees. App code that fetches /app-users itself on every page to show the people of the app.\n\n_This is guidance to help the user prepare, not legal or tax advice. Rules differ per country and change over time; for anything that matters, the user checks with an advisor or the official sources (in the Netherlands for example ACM ConsuWijzer for consumer law, the Belastingdienst for VAT and the Autoriteit Persoonsgegevens for privacy)._"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/readiness": {
      "get": {
        "operationId": "checkReadiness",
        "tags": [
          "Sites"
        ],
        "summary": "Check what works on a site and what is missing before going live",
        "description": "Reads the live files of a site, detects its kind (or takes type) and checks what can be checked automatically: terms, privacy, returns and contact pages with company details; forms that post to a real collection (not nowhere, not only mailto, not localStorage); buy buttons and a cart that lead to a real checkout; prices with currency; no placeholder text; no test wording on a live project; internal links that work; a 404 page; no password checked in JavaScript. It also checks findability: a title (10 to 60 characters) and a meta description (50 to 160) per page, exactly one h1, the page language, a share preview (og:title, og:description and an absolute og:image), alt text on images, images over 500 KB, a viewport meta tag and a favicon; and structured data (JSON-LD): existing blocks are checked for valid JSON, their required fields and leftover placeholders, and when there is none and the kind is known it offers a ready snippet to fill with the user's real details. Returns what works, what is missing (each with a plain explanation and the next tool), and what only the owner can confirm (check_with_owner). Advice only: it never blocks a deploy.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "The kind of application; leave it out to detect it from the site.",
            "schema": {
              "type": "string",
              "enum": [
                "webshop",
                "bookings",
                "restaurant",
                "course_membership",
                "wordpress_portal"
              ]
            },
            "example": "webshop"
          }
        ],
        "responses": {
          "200": {
            "description": "What works, what is missing, and what to check with the owner.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteReadiness"
                },
                "example": {
                  "site": {
                    "id": "7a1c2e0f-3b4d-4e5f-8a9b-0c1d2e3f4a5b",
                    "url": "https://bakkerij-jansen.hypamoon.app",
                    "live": true
                  },
                  "type": "webshop",
                  "type_source": "given",
                  "region": "eu",
                  "country": "NL",
                  "works": [
                    {
                      "id": "privacy_page",
                      "title": "Privacy statement",
                      "detail": "Found /privacy, linked from the site."
                    },
                    {
                      "id": "contact_details",
                      "title": "Contact and company details",
                      "detail": "Found an email address, an address, the KvK number."
                    },
                    {
                      "id": "forms_saved",
                      "title": "Forms save what visitors send",
                      "detail": "1 form found, each sent to collection contact."
                    },
                    {
                      "id": "no_fake_login",
                      "title": "No password checked in JavaScript",
                      "detail": "No password is checked in JavaScript."
                    },
                    {
                      "id": "no_local_storage_db",
                      "title": "No visitor data kept in localStorage",
                      "detail": "localStorage is not used for visitor data."
                    },
                    {
                      "id": "no_test_wording",
                      "title": "No test wording on the live site",
                      "detail": "No test wording found on the live site."
                    },
                    {
                      "id": "prices_currency",
                      "title": "Prices with currency",
                      "detail": "Prices are shown with their currency."
                    },
                    {
                      "id": "no_placeholders",
                      "title": "No placeholder text",
                      "detail": "No placeholder text found."
                    },
                    {
                      "id": "seo_h1",
                      "title": "One main heading per page",
                      "detail": "Every page has exactly one h1 heading (all 4 pages checked)."
                    },
                    {
                      "id": "seo_image_alt",
                      "title": "Alt text on images",
                      "detail": "The pages have no <img> elements."
                    },
                    {
                      "id": "seo_image_size",
                      "title": "Image file sizes",
                      "detail": "The site has no image files of its own."
                    }
                  ],
                  "missing": [
                    {
                      "id": "terms_page",
                      "title": "Terms or policy page",
                      "explanation": "No terms and conditions page was found. Before you go live, you might want to add one and link it in the footer.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add /terms with keep_existing: true and link it from the footer of every page. Write it with the user; never invent their policies."
                      }
                    },
                    {
                      "id": "returns_page",
                      "title": "Returns and withdrawal page",
                      "explanation": "No returns or withdrawal page was found. Before you go live, you might want to add one and link it in the footer.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add /returns with the 14-day withdrawal information and the model withdrawal form with keep_existing: true and link it from the footer of every page. Write it with the user; never invent their policies."
                      }
                    },
                    {
                      "id": "checkout_real",
                      "title": "Buttons and cart lead somewhere real",
                      "explanation": "The button \"In winkelwagen\" on /index.html does nothing: it is outside a form and no script handles it. The link \"Bestel nu\" on /producten.html goes nowhere (href #). The site has a cart, but nothing leads to a real checkout or an order request; a visitor can fill the cart and never order.",
                      "next_step": {
                        "tool": "get_checkout_snippet",
                        "how": "Use the real checkout (get_checkout_snippet) or a form that saves (get_form_snippet); if that is not available, change the text so it does not promise it."
                      },
                      "paths": [
                        "/index.html",
                        "/producten.html"
                      ]
                    },
                    {
                      "id": "internal_links",
                      "title": "Internal links work",
                      "explanation": "1 internal link lead to a page that does not exist: /voorwaarden (on /producten).",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add the missing pages or fix the links, with keep_existing: true."
                      },
                      "paths": [
                        "/producten.html"
                      ]
                    },
                    {
                      "id": "not_found_page",
                      "title": "Not-found page",
                      "explanation": "The site has no own not-found page; visitors who follow an old link see a plain error.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add /404.html with a link back home and the contact details, with keep_existing: true."
                      }
                    },
                    {
                      "id": "seo_title",
                      "title": "Page titles for search",
                      "explanation": "3 pages without a good title: /producten (no title), /contact (no title), /privacy (no title). Search engines show the title as the headline of the result. Before you go live, you might want to give every page its own title of 10 to 60 characters.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add a <title> of 10 to 60 characters to the head of each page, saying what that page is about (like \"Fresh sourdough bread | Bakery name\"), and deploy with keep_existing: true."
                      },
                      "paths": [
                        "/producten.html",
                        "/contact.html",
                        "/privacy.html"
                      ]
                    },
                    {
                      "id": "seo_description",
                      "title": "Meta descriptions for search",
                      "explanation": "4 pages without a good meta description: / (no description), /producten (no description), /contact (no description), /privacy (no description). Search engines often show it as the text under the headline. Before you go live, you might want to give every page a short description of 50 to 160 characters.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add <meta name=\"description\" content=\"...\"> of 50 to 160 characters to the head of each page, in the user's own words about that page, and deploy with keep_existing: true."
                      },
                      "paths": [
                        "/index.html",
                        "/producten.html",
                        "/contact.html",
                        "/privacy.html"
                      ]
                    },
                    {
                      "id": "seo_lang",
                      "title": "Page language set",
                      "explanation": "3 pages without a language: /producten (no lang), /contact (no lang), /privacy (no lang). The lang attribute helps search engines show the page to the right audience and lets screen readers pronounce it correctly. Before you go live, you might want to add it.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Set <html lang=\"nl\"> (or the language the site is written in, like en or de) on each page and deploy with keep_existing: true."
                      },
                      "paths": [
                        "/producten.html",
                        "/contact.html",
                        "/privacy.html"
                      ]
                    },
                    {
                      "id": "seo_open_graph",
                      "title": "Share preview (Open Graph)",
                      "explanation": "4 pages without a complete share preview: / (no og:title, og:description, og:image), /producten (no og:title, og:description, og:image), /contact (no og:title, og:description, og:image), /privacy (no og:title, og:description, og:image). When someone shares the link in WhatsApp, LinkedIn or Facebook, these tags decide the title, text and image of the preview. Before you go live, you might want to add og:title, og:description and og:image.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add <meta property=\"og:title\">, <meta property=\"og:description\"> and <meta property=\"og:image\" content=\"https://...\"> (a full https address to an image of about 1200 by 630 pixels) to the head of each page, and deploy with keep_existing: true."
                      },
                      "paths": [
                        "/index.html",
                        "/producten.html",
                        "/contact.html",
                        "/privacy.html"
                      ]
                    },
                    {
                      "id": "seo_viewport",
                      "title": "Mobile viewport",
                      "explanation": "4 pages without a viewport meta tag: / (no viewport), /producten (no viewport), /contact (no viewport), /privacy (no viewport). Without it phones show the page zoomed out and tiny, and search engines treat it as not mobile friendly. Before you go live, you might want to add it.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> to the head of each page and deploy with keep_existing: true."
                      },
                      "paths": [
                        "/index.html",
                        "/producten.html",
                        "/contact.html",
                        "/privacy.html"
                      ]
                    },
                    {
                      "id": "seo_favicon",
                      "title": "Favicon",
                      "explanation": "The site has no favicon, the small icon in the browser tab and next to the site in search results. Sites on hypamoon.app get no default icon, so browsers show an empty one. Before you go live, you might want to add one.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add /favicon.ico or /favicon.svg (the user's logo, or a simple icon from their initial) and <link rel=\"icon\" href=\"/favicon.svg\"> in the head of each page, with keep_existing: true."
                      }
                    },
                    {
                      "id": "structured_data",
                      "title": "Structured data for search",
                      "explanation": "The site has no structured data (JSON-LD). Before you go live, you might want to add a Product block so search engines can show more than a plain link: Search engines can show the product with its price and whether it is in stock. Use one block per product page, with that product's own details.",
                      "next_step": {
                        "tool": "deploy_site",
                        "how": "Add this block to the head of each product page (one block per product, with that product's details), replace every YOUR ... placeholder with the user's real details (ask them; never invent an address, phone number, price or hours, and leave out optional fields you do not know), then deploy with keep_existing: true: <script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"Product\",\"name\":\"YOUR PRODUCT NAME\",\"description\":\"YOUR PRODUCT DESCRIPTION\",\"image\":\"YOUR PRODUCT IMAGE URL (absolute, https)\",\"offers\":{\"@type\":\"Offer\",\"price\":\"YOUR PRICE (like 19.95)\",\"priceCurrency\":\"YOUR CURRENCY CODE (like EUR)\",\"availability\":\"https://schema.org/InStock\",\"url\":\"YOUR PRODUCT PAGE URL\"}}</script>"
                      }
                    }
                  ],
                  "check_with_owner": [
                    {
                      "id": "vat_number",
                      "level": "usually_required",
                      "description": "The VAT number on the site, if the business has one (not every small business or private seller does).",
                      "why": "Businesses with a VAT number are usually expected to show it on their site.",
                      "how_on_hypamoon": "Ask the user for these details (never invent them), put them on /contact and in the footer of every page with deploy_site."
                    },
                    {
                      "id": "cookie_notice",
                      "level": "recommended",
                      "description": "A cookie notice with a real choice, but only if the site uses non-essential cookies or trackers (analytics with personal data, advertising pixels, embedded videos that set cookies). A site with only functional storage needs no banner.",
                      "why": "The ePrivacy rules (EU) and PECR (UK) require consent before non-essential cookies are set. A banner that is not needed only gets in the way.",
                      "how_on_hypamoon": "Hypamoon sites, forms, checkout and the chat widget need no cookie banner by themselves. If the user adds a tracker, add a consent banner that loads it only after a yes."
                    },
                    {
                      "id": "accessibility",
                      "level": "recommended",
                      "description": "Basic accessibility: readable contrast, alt text on images, labels on form fields, keyboard navigation, and an accessibility statement for online shops and services.",
                      "why": "The European Accessibility Act applies to e-commerce and many consumer services since 28 June 2025; micro-enterprises (fewer than 10 staff and at most 2 million euro turnover) are usually exempt for services. Accessible sites also sell better.",
                      "how_on_hypamoon": "Build the pages with labels, alt text and enough contrast; add /accessibility if the business is not a micro-enterprise."
                    },
                    {
                      "id": "withdrawal_function",
                      "level": "usually_required",
                      "description": "An easy way to withdraw online: a clearly labelled withdrawal function (like \"withdraw from contract here\") where the customer confirms their withdrawal, followed by a confirmation.",
                      "why": "An EU amendment to the Consumer Rights Directive (Directive 2023/2673) asks for an online withdrawal function for contracts made online, applying from 19 June 2026.",
                      "how_on_hypamoon": "A form on /returns that saves the withdrawal in a collection (create_collection with public_write and email_on_insert, get_form_snippet) and shows a confirmation."
                    },
                    {
                      "id": "shipping_costs_upfront",
                      "level": "usually_required",
                      "description": "Shipping costs, delivery restrictions (countries) and the expected delivery time shown before the customer starts ordering.",
                      "why": "In the EU and UK this information must be clear at the start of the ordering process; everywhere, hidden costs at the end are the main reason people abandon a purchase.",
                      "how_on_hypamoon": "Show shipping costs on the product pages and next to the order button (deploy_site)."
                    },
                    {
                      "id": "payment_methods_upfront",
                      "level": "usually_required",
                      "description": "The accepted payment methods shown at the latest at the start of the ordering process.",
                      "why": "The Consumer Rights Directive asks for this at the start of the order.",
                      "how_on_hypamoon": "List the methods of the user's payment account on the product page or in the footer."
                    },
                    {
                      "id": "order_button_text",
                      "level": "usually_required",
                      "description": "The final order button says clearly that ordering means paying, like \"Order with obligation to pay\" or in Dutch \"Bestellen en betalen\"; not just \"Next\" or \"Continue\".",
                      "why": "The EU Consumer Rights Directive and the UK rules require this; without it the customer may not be bound by the order.",
                      "how_on_hypamoon": "Set the button text on the checkout button (get_checkout_snippet html) or the order request button."
                    },
                    {
                      "id": "order_confirmation",
                      "level": "usually_required",
                      "description": "An order confirmation by email (a durable medium) with what was ordered, the price, the company details and the withdrawal information.",
                      "why": "EU and UK consumer law ask for a confirmation on a durable medium at the latest at delivery; customers everywhere expect one right away.",
                      "how_on_hypamoon": "Check what the checkout or the payment provider already sends; add one with send_email (Mail module) if needed, or tell the user to send it."
                    },
                    {
                      "id": "legal_guarantee",
                      "level": "recommended",
                      "description": "A line about the legal guarantee: products must be as promised, and consumers keep their legal rights when something is wrong.",
                      "why": "The legal guarantee applies anyway (at least two years in most EU countries; in the Netherlands what the buyer may reasonably expect); saying so avoids disputes.",
                      "how_on_hypamoon": "Add it to /terms or /returns with deploy_site."
                    },
                    {
                      "id": "complaints",
                      "level": "recommended",
                      "description": "How to complain, with an email address and a reasonable answer time, and the dispute body if the business is a member of one (in the Netherlands for example the Geschillencommissie or a trade association).",
                      "why": "The EU ODR platform closed on 20 July 2025 and the link is no longer needed, but a clear complaint route is still expected, and members of a dispute body must mention it.",
                      "how_on_hypamoon": "Add it to /terms or /contact with deploy_site."
                    },
                    {
                      "id": "age_check",
                      "level": "usually_required",
                      "description": "An age check when selling age-restricted products (alcohol, tobacco and vapes, knives, some games): ask for confirmation online and check the ID at delivery.",
                      "why": "Selling these to minors is forbidden; in the Netherlands the Alcoholwet requires an age check at the door for alcohol delivered to the home.",
                      "how_on_hypamoon": "Only if relevant: add an age statement to the order form and tell the user to arrange the check at delivery."
                    },
                    {
                      "id": "vat_registration",
                      "level": "recommended",
                      "description": "VAT set up correctly: the right rate in the prices, and the EU One Stop Shop (OSS) when sales to consumers in other EU countries pass 10,000 euro a year.",
                      "why": "Above that threshold the VAT of the buyer's country applies.",
                      "how_on_hypamoon": "Nothing to build; mention it to the user so they check it with their accountant."
                    }
                  ],
                  "pages_checked": 4,
                  "note": "Advice only: nothing here blocks a deploy or requires the user to answer anything. Share what is missing in plain words and offer to fix it. This is guidance to help the user prepare, not legal or tax advice. Rules differ per country and change over time; for anything that matters, the user checks with an advisor or the official sources (in the Netherlands for example ACM ConsuWijzer for consumer law, the Belastingdienst for VAT and the Autoriteit Persoonsgegevens for privacy).",
                  "summary_markdown": "## Readiness of https://bakkerij-jansen.hypamoon.app (webshop)\n**Works (11):** Privacy statement; Contact and company details; Forms save what visitors send; No password checked in JavaScript; No visitor data kept in localStorage; No test wording on the live site; Prices with currency; No placeholder text; One main heading per page; Alt text on images; Image file sizes.\n**Before you go live, you might want to look at (12):**\n- Terms or policy page: No terms and conditions page was found. Before you go live, you might want to add one and link it in the footer. Next: deploy_site.\n- Returns and withdrawal page: No returns or withdrawal page was found. Before you go live, you might want to add one and link it in the footer. Next: deploy_site.\n- Buttons and cart lead somewhere real: The button \"In winkelwagen\" on /index.html does nothing: it is outside a form and no script handles it. The link \"Bestel nu\" on /producten.html goes nowhere (href #). The site has a cart, but nothing leads to a real checkout or an order request; a visitor can fill the cart and never order. Next: get_checkout_snippet.\n- Internal links work: 1 internal link lead to a page that does not exist: /voorwaarden (on /producten). Next: deploy_site.\n- Not-found page: The site has no own not-found page; visitors who follow an old link see a plain error. Next: deploy_site.\n- Page titles for search: 3 pages without a good title: /producten (no title), /contact (no title), /privacy (no title). Search engines show the title as the headline of the result. Before you go live, you might want to give every page its own title of 10 to 60 characters. Next: deploy_site.\n- Meta descriptions for search: 4 pages without a good meta description: / (no description), /producten (no description), /contact (no description), /privacy (no description). Search engines often show it as the text under the headline. Before you go live, you might want to give every page a short description of 50 to 160 characters. Next: deploy_site.\n- Page language set: 3 pages without a language: /producten (no lang), /contact (no lang), /privacy (no lang). The lang attribute helps search engines show the page to the right audience and lets screen readers pronounce it correctly. Before you go live, you might want to add it. Next: deploy_site.\n- Share preview (Open Graph): 4 pages without a complete share preview: / (no og:title, og:description, og:image), /producten (no og:title, og:description, og:image), /contact (no og:title, og:description, og:image), /privacy (no og:title, og:description, og:image). When someone shares the link in WhatsApp, LinkedIn or Facebook, these tags decide the title, text and image of the preview. Before you go live, you might want to add og:title, og:description and og:image. Next: deploy_site.\n- Mobile viewport: 4 pages without a viewport meta tag: / (no viewport), /producten (no viewport), /contact (no viewport), /privacy (no viewport). Without it phones show the page zoomed out and tiny, and search engines treat it as not mobile friendly. Before you go live, you might want to add it. Next: deploy_site.\n- Favicon: The site has no favicon, the small icon in the browser tab and next to the site in search results. Sites on hypamoon.app get no default icon, so browsers show an empty one. Before you go live, you might want to add one. Next: deploy_site.\n- Structured data for search: The site has no structured data (JSON-LD). Before you go live, you might want to add a Product block so search engines can show more than a plain link: Search engines can show the product with its price and whether it is in stock. Use one block per product page, with that product's own details. Next: deploy_site.\n**Check with the owner (12):** vat_number, cookie_notice, accessibility, withdrawal_function, shipping_costs_upfront, payment_methods_upfront, order_button_text, order_confirmation, legal_guarantee, complaints, age_check, vat_registration.\n\n_This is advice, not a gate: it never blocks a deploy. This is guidance to help the user prepare, not legal or tax advice. Rules differ per country and change over time; for anything that matters, the user checks with an advisor or the official sources (in the Netherlands for example ACM ConsuWijzer for consumer law, the Belastingdienst for VAT and the Autoriteit Persoonsgegevens for privacy)._"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/project/move-to-live": {
      "post": {
        "operationId": "moveToLive",
        "tags": [
          "Project"
        ],
        "summary": "Move sites from the test project to the live project",
        "description": "Moves the chosen sites of the test project (default: all) to its live project, with every version, label, password and address, the collections they use with all documents, an app on the site with its sign-in users, files and database, the agents on those collections (paused) and domains connected for real. Simulated domains do not come along. The test form token (hm_pubtest_) in the site files is replaced by a new live token with the same allowed origins, as a new site version labelled 'moved to live'. A collection that a site staying in test also uses is refused by default (409 with the list); shared_collections copy gives the live project its own copy. Everything in the database happens in one transaction, and a move log keeps the rows from before, so the move can be undone within 24 hours. Works with a test or a live key of the same project, but only for an owner (through OAuth, or a key made by an owner) and only when the live project is verified: otherwise verification_required with the missing steps (company details, a payment with SCA, a verified phone). The plan limits of the live project apply (sites, files, data, apps). A chat connected through OAuth to the test project stays on test: reconnect and choose the live project, or switch the connection to live in the portal under Connections.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveToLiveInput"
              },
              "example": {
                "sites": [
                  "bakkerij-jansen"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What moved, what stayed, and the next step.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoveToLive"
                },
                "example": {
                  "id": "0f6c2a8e-7d3b-4c1e-9a55-2b8f6d4e1c90",
                  "status": "done",
                  "from_project": {
                    "id": "6b1d2c3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
                    "name": "Bakkerij Jansen (test)",
                    "mode": "test"
                  },
                  "to_project": {
                    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                    "name": "Bakkerij Jansen",
                    "mode": "live"
                  },
                  "moved": {
                    "sites": [
                      {
                        "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                        "slug": "bakkerij-jansen",
                        "url": "https://bakkerij-jansen.hypamoon.app",
                        "new_version": 7,
                        "form_token_replaced": true
                      }
                    ],
                    "collections": [
                      {
                        "id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
                        "name": "contact",
                        "documents": 12
                      }
                    ],
                    "copied_collections": [],
                    "apps": [],
                    "agents": [],
                    "domains": [],
                    "form_tokens": [
                      {
                        "name": "Website form",
                        "live_token_id": "9c0d1e2f-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
                        "replaces_test_token_id": "2f3a4b5c-6d7e-4f8a-9b0c-1d2e3f4a5b6c"
                      }
                    ],
                    "webhooks": 0
                  },
                  "stayed_in_test": {
                    "sites": [],
                    "collections": [],
                    "simulated_domains": []
                  },
                  "warnings": [],
                  "errors": [],
                  "undo": {
                    "until": "2026-10-10T09:00:00.000Z",
                    "how": "undo_move_to_live with move_id 0f6c2a8e-7d3b-4c1e-9a55-2b8f6d4e1c90 (POST /project/move-to-live/undo with {\"move_id\": \"0f6c2a8e-7d3b-4c1e-9a55-2b8f6d4e1c90\"}), until 2026-10-10T09:00:00.000Z."
                  },
                  "connection": {
                    "mode": "test",
                    "note": "This connection still points to the test project, where these sites no longer are. It keeps working there until it is reconnected or switched."
                  },
                  "summary": "Moved 1 site (bakkerij-jansen) with all versions, 1 collection with 12 documents and a new live form token from Bakkerij Jansen (test) to Bakkerij Jansen. Nothing else was in the test project. This can be undone until 2026-10-10T09:00:00.000Z with undo_move_to_live.",
                  "next_step": "Reconnect your chat to Hypamoon and choose Bakkerij Jansen (not test), or switch this connection in the portal under Connections (Switch to live; you confirm the access again there). Check the sites at their address; forms now save to the live project. To put the user's own domain on a site: connect_domain with the site_id from the live project (through the nameservers it is free)."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/project/move-to-live/undo": {
      "post": {
        "operationId": "undoMoveToLive",
        "tags": [
          "Project"
        ],
        "summary": "Undo a move to live",
        "description": "Moves the sites, collections, apps and agents of a move back to the test project, within 24 hours. The sites get the test form token back as a new version, and the live token is revoked. A copied collection stays in the live project. Without move_id the latest move of this project. After 24 hours: 409 with how support can still help.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveToLiveUndoInput"
              },
              "example": {
                "move_id": "0f6c2a8e-7d3b-4c1e-9a55-2b8f6d4e1c90"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What went back.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoveToLiveUndo"
                },
                "example": {
                  "id": "0f6c2a8e-7d3b-4c1e-9a55-2b8f6d4e1c90",
                  "status": "undone",
                  "sites": [
                    "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f"
                  ],
                  "collections": 1,
                  "apps": 0,
                  "agents": 0,
                  "domains": 0,
                  "errors": [],
                  "summary": "Moved 1 site and 1 collection back from Bakkerij Jansen to Bakkerij Jansen (test).",
                  "next_step": "The sites are in the test project again, with the test form token. Use the test connection or a test key to work on them."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/search-console/verify": {
      "post": {
        "operationId": "verifySearchConsole",
        "tags": [
          "Sites"
        ],
        "summary": "Verify a site or domain in Google Search Console",
        "description": "Proves to Google Search Console that a site or domain belongs to the user, with the token from Search Console (the value alone, google-site-verification=..., or the whole meta tag). method dns adds the TXT record google-site-verification=<token> to a domain connected in this project with nameservers (Search Console property type Domain). method meta adds the meta tag to /index.html as a new site version labelled \"search console verification\" (property type URL prefix; also on <slug>.hypamoon.app). A site that is unlisted, behind a password or paid access, or offline is refused: Google cannot read it. Give site_id or domain. Calling it again with the same token changes nothing. Returns the property, the next steps for the user in Search Console and the sitemap to submit.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchConsoleVerifyInput"
              },
              "example": {
                "domain": "bakkerijjansen.nl",
                "method": "dns",
                "token": "Abc123dEf456gHi789jKl012mNo345pQr678sTu901v"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was added and what the user does next.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchConsoleVerification"
                },
                "example": {
                  "method": "dns",
                  "status": "added",
                  "site_id": "5b1f3c2a-8d4e-4f6a-9b7c-1d2e3f4a5b6c",
                  "domain": "bakkerijjansen.nl",
                  "property": "sc-domain:bakkerijjansen.nl",
                  "record": {
                    "type": "TXT",
                    "name": "bakkerijjansen.nl",
                    "content": "google-site-verification=Abc123dEf456gHi789jKl012mNo345pQr678sTu901v"
                  },
                  "sitemap": "https://bakkerijjansen.nl/sitemap.xml",
                  "next_steps": [
                    "In Search Console (https://search.google.com/search-console), add a property of the type Domain for bakkerijjansen.nl (the one you got this token for) and click Verify.",
                    "Keep the TXT record: removing it ends the verification.",
                    "After verifying, go to Sitemaps and submit https://bakkerijjansen.nl/sitemap.xml."
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/stats": {
      "get": {
        "operationId": "getSiteStats",
        "tags": [
          "Sites"
        ],
        "summary": "Get the visits of a site",
        "description": "Page views of a site of this project per day, the top pages and where visitors came from (direct, internal, search, social, email, shortener, external). Counted at the edge without cookies; crawlers and scripts are left out. Page views, not unique visitors. Data is kept for 90 days; paths and the bot filter exist since 9 October 2026.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "days",
            "in": "query",
            "description": "How many days back, today included (default 30).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 30
            },
            "example": 7
          }
        ],
        "responses": {
          "200": {
            "description": "Page views per day, top pages and origins.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteStats"
                },
                "example": {
                  "site_id": "5b1f3c2a-8d4e-4f6a-9b7c-1d2e3f4a5b6c",
                  "slug": "bakkerij-jansen",
                  "days": 7,
                  "from": "2026-10-03",
                  "to": "2026-10-09",
                  "totals": {
                    "page_views": 60,
                    "bot_page_views_excluded": 41
                  },
                  "per_day": [
                    {
                      "date": "2026-10-03",
                      "page_views": 0
                    },
                    {
                      "date": "2026-10-04",
                      "page_views": 0
                    },
                    {
                      "date": "2026-10-05",
                      "page_views": 12
                    },
                    {
                      "date": "2026-10-06",
                      "page_views": 0
                    },
                    {
                      "date": "2026-10-07",
                      "page_views": 30
                    },
                    {
                      "date": "2026-10-08",
                      "page_views": 0
                    },
                    {
                      "date": "2026-10-09",
                      "page_views": 18
                    }
                  ],
                  "top_pages": [
                    {
                      "path": "/",
                      "page_views": 34
                    },
                    {
                      "path": "/menu",
                      "page_views": 19
                    },
                    {
                      "path": "/contact",
                      "page_views": 7
                    }
                  ],
                  "origins": [
                    {
                      "origin": "search",
                      "page_views": 28
                    },
                    {
                      "origin": "direct",
                      "page_views": 20
                    },
                    {
                      "origin": "social",
                      "page_views": 12
                    }
                  ],
                  "note": "Page views by people, counted at the edge without cookies; not unique visitors. Numbers can be estimates on busy days (sampling). Paths and the bot filter exist since 9 October 2026; older data is kept for 90 days."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/{id}/fetch": {
      "get": {
        "operationId": "fetchSitePage",
        "tags": [
          "Sites"
        ],
        "summary": "Fetch a page of a site or app, live",
        "description": "Fetches one path of a site or app of this project from the public internet, through Hypamoon's server, and returns the status, the main headers (never set-cookie), the final URL after redirects and the start of the body as text. The way to prove that a deployed site or app endpoint works live, for a client that cannot open the address itself. Only addresses of this project: the site's own address (also the secret link of an unlisted site) and its connected domains; give path (on the site's address) or a full url on one of them. Redirects are followed up to 3 times, and only to addresses of this project; otherwise redirect_blocked says why and the redirect itself is returned. GET or HEAD only, without cookies or keys, a 10 second timeout per hop, the body cut at 20 KB (truncated), private addresses refused. At most 30 requests per minute per project (429 rate_limited). A site that does not answer gives 502 upstream_error.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the site.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "path",
            "in": "query",
            "description": "The path on the site's address, like / or /contact or /api/health, with a query if needed. Give this or url.",
            "schema": {
              "type": "string",
              "maxLength": 2000,
              "default": "/"
            },
            "example": "/contact"
          },
          {
            "name": "url",
            "in": "query",
            "description": "A full https URL on an address of this site, like a connected domain. Give this or path.",
            "schema": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "example": "https://bakkerijjansen.nl/menu"
          },
          {
            "name": "method",
            "in": "query",
            "description": "GET (default) or HEAD.",
            "schema": {
              "type": "string",
              "enum": [
                "GET",
                "HEAD"
              ],
              "default": "GET"
            },
            "example": "GET"
          }
        ],
        "responses": {
          "200": {
            "description": "What the site answered.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitePage"
                },
                "example": {
                  "url": "https://bakkerij-jansen.hypamoon.app/contact",
                  "final_url": "https://bakkerij-jansen.hypamoon.app/contact",
                  "method": "GET",
                  "status": 200,
                  "ok": true,
                  "headers": {
                    "content-type": "text/html; charset=utf-8",
                    "cache-control": "public, max-age=60"
                  },
                  "redirects": [],
                  "content_type": "text/html",
                  "body": "<!doctype html><html lang=\"nl\"><head><title>Contact | Bakkerij Jansen</title>",
                  "body_bytes": 4210,
                  "truncated": false,
                  "elapsed_ms": 182
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains": {
      "get": {
        "operationId": "listDomains",
        "tags": [
          "Domains"
        ],
        "summary": "List the domains",
        "description": "The domains of the project, newest first, each with its status, nameservers, site and next_step.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The domains.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "connectDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Connect a domain the user owns",
        "description": "Connects a domain. method nameservers (default): we create a DNS zone for it, copy its existing records (mail included), and return the two nameservers the user sets at their registrar. method cname: www (for a domain) or one subdomain (like shop.example.com), through a CNAME and one TXT record (proof of ownership) at the user's own DNS provider; the certificate is issued and renewed automatically. Connecting through the nameservers is free on every plan; the cname method carries a surcharge of EUR 2 a month excl. VAT (cname_surcharge), except for a subdomain of a domain the project already has on Hypamoon's nameservers, which is free. Registering a new domain costs its registration price. Connecting a domain that is already in this project only changes its site. A subdomain that still has an A, AAAA or CNAME record pointing elsewhere in the zone here returns confirmation_required with preflight, diagnosis (what it points to and what answers there) and fix (the same call with replace_existing true); undo_dns_change puts the old record back. Every error of a domain route carries diagnosis and fix.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainConnectInput"
              },
              "example": {
                "domain": "example.com",
                "site_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The domain was already connected; its site is updated.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "201": {
            "description": "The domain, with nameservers, mail_check and next_step.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, confirmation_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "confirmation_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/confirmation_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/health": {
      "get": {
        "operationId": "listDomainHealth",
        "tags": [
          "Domains"
        ],
        "summary": "List watched domains and their health",
        "description": "The domains this project watches (watch_domain), each with status ok, warning or problem, the reason in one sentence, the findings (dangling DNS that points to a host where the domain is not set up, spam on the homepage, a changed title or host, expiry in 30 or 7 days, the certificate, and mail advice), whether ownership is proven, and whether the domain is parked. Checked once a day; the owner gets one mail per domain per day at most, for a new problem. Watching is included in the plan, up to limit domains. dns_cleanup lists, for every domain of the project on Hypamoon's nameservers (watched or not), how many records the previous host left behind: safe_to_remove and ask; GET /domains/{id}/dns-cleanup has the details.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The watched domains.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/watch": {
      "post": {
        "operationId": "watchDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Watch a domain, or park it",
        "description": "Watches a domain the user owns, registered anywhere, without connecting it to a site. Ownership is proven with the same TXT record as connect_domain (ownership.challenge); a domain that is already active in this project or bought through Hypamoon needs none. Until it is proven the status is pending (202) and nothing is checked. park page or redirect also moves the domain to Hypamoon's nameservers (like connect_domain without a site) and then shows a neutral page or sends visitors to redirect_to with a 301; a domain without mail gets a null MX, SPF -all and DMARC p=reject against spoofing. park off stops parking. remove true stops watching.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 300,
                    "description": "The domain, like example.com. It can be registered anywhere."
                  },
                  "expected_hosts": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "vercel",
                        "netlify",
                        "github",
                        "heroku",
                        "aws",
                        "azure",
                        "shopify",
                        "fly",
                        "render"
                      ]
                    },
                    "maxItems": 10,
                    "description": "Optional. Where the domain is hosted on purpose (vercel, netlify, github, heroku, aws, azure, shopify, fly, render). If its DNS points to another one of these, that is a problem. Leave it out and we remember the host from the first check, and warn when it changes."
                  },
                  "expected_language": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-z]{2}$",
                    "description": "Optional. The language of the site, as a two-letter code like en or nl. A homepage in another language is a warning."
                  },
                  "park": {
                    "type": "string",
                    "enum": [
                      "off",
                      "page",
                      "redirect"
                    ],
                    "description": "Optional. page: park the domain on Hypamoon with a neutral page (\"This domain is reserved\"). redirect: park it and send visitors to redirect_to with a 301. off: stop parking. Parking moves the domain's DNS to Hypamoon (nameservers), so nobody can claim it elsewhere."
                  },
                  "redirect_to": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "Only with park redirect: the address to send visitors to, like https://example.com. Path and query of the request are kept."
                  },
                  "remove": {
                    "type": "boolean",
                    "description": "true: stop watching the domain. Parking stays as it is."
                  }
                },
                "required": [
                  "domain"
                ],
                "additionalProperties": false
              },
              "example": {
                "domain": "example.com",
                "expected_hosts": [
                  "netlify"
                ],
                "park": "page"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The watched domain, with its health.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "202": {
            "description": "Watched, waiting for the ownership proof in ownership.challenge.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/dns-cleanup": {
      "get": {
        "operationId": "getDnsCleanup",
        "tags": [
          "Domains"
        ],
        "summary": "Suggest a cleanup of left-over DNS records",
        "description": "For a domain on Hypamoon's nameservers: which records were left behind by the previous host, such as the names a hosting control panel (cPanel, Plesk, DirectAdmin) creates by itself, old certificate tokens and names that still point to the previous host. Each record has a label (safe_to_remove, ask or keep), a group and the reason. Microsoft 365, Google Workspace, everything Hypamoon manages, SPF, DMARC and the domain's MX are always keep. spf_advice suggests how to tighten the SPF; it is never changed by a cleanup. Names that point to the previous host are checked over https (live_checked). recent_cleanups lists the cleanups of the last 30 days. Read-only.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cleanup suggestion.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "cleanUpDns",
        "tags": [
          "Domains"
        ],
        "summary": "Remove left-over DNS records in one step",
        "description": "Removes the records labeled safe_to_remove, minus exclude_record_ids, plus the ask-records in include_record_ids, in one step. The suggestion is made again at that moment; a record labeled keep is never removed (422). Before anything is removed, the records are written to the audit log as a zone file; the answer has the same zone_file and a cleanup_id. For 30 days the cleanup can be undone with POST /domains/{id}/dns-cleanup/restore.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "include_record_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 500,
                    "description": "Optional. Records labeled ask (from get_dns_cleanup) that the user chose to remove too. Records labeled keep are never removed; naming one is refused."
                  },
                  "exclude_record_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 500,
                    "description": "Optional. Records labeled safe_to_remove that the user wants to keep this time."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "include_record_ids": [
                  "372e67954025e0ba6aaa6d586b9e0b59"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was removed, what failed, the zone file and until when it can be restored.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/dns-cleanup/restore": {
      "post": {
        "operationId": "restoreDnsCleanup",
        "tags": [
          "Domains"
        ],
        "summary": "Put the records of a cleanup back",
        "description": "Within 30 days of a cleanup: adds the removed records again, only those that are missing now. A name that now serves a Hypamoon site is skipped. Records that were put back are not suggested for a cleanup again unless the user chooses them.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cleanup_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The id of the cleanup to undo (recent_cleanups in get_dns_cleanup)."
                  }
                },
                "required": [
                  "cleanup_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "cleanup_id": "0b6f0e0e-2a7c-4d39-9a8e-1d3c5b7a9e21"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was added again, what was already there and what failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/check": {
      "get": {
        "operationId": "checkDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Check whether a domain is available",
        "description": "Whether the domain can be registered, the price of the first year and of a renewal, and the URL of the registry's terms. Results are cached for a minute; checks are rate limited per project.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "The domain, like example.com.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The check.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/suggestions": {
      "get": {
        "operationId": "suggestDomains",
        "tags": [
          "Domains"
        ],
        "summary": "Suggest available domains",
        "description": "Available domains based on a few words, or on names you came up with yourself. With query, Hypamoon makes the candidates: the words themselves on .nl and .com first, then short variants like getbakery, trybakery and bakeryhq. With names, up to ten names of your own are checked in one call; a name without an extension is tried on .nl and .com first. At most ten names are checked per call, and each one counts as a check in the rate limit. unavailable lists the names that are taken; rejected lists the names that are not valid.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "description": "A few words, like 'corner bakery'. Required without names.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tlds",
            "in": "query",
            "description": "Optional. Comma-separated extensions, like nl,com.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "names",
            "in": "query",
            "description": "Optional. Up to ten comma-separated names to check, with or without extension, like getcrumb,crumbhq.com.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The available domains.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/register": {
      "post": {
        "operationId": "registerDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Register a domain",
        "description": "Registers the domain in the user's name, with DNS and DNSSEC set up here, and whois privacy where the extension allows it. accept_registry_terms must be true: the user explicitly accepted the registry's terms (registry_terms_url from the check). max_price_eur is the total the user confirmed for all years; a higher price is refused. A name with a higher registry price (premium in the check) is bought for 1 year only, with accept_price_eur exactly the price the user confirmed, and only up to a ceiling; if the registry price changed just before the purchase, nothing is bought. Needs an Idempotency-Key; the same key returns the same domain, and a key whose attempt failed is never retried (use a new key). In live mode the price is deducted from the balance; in test mode nothing is bought. Real purchases in live mode are not open yet: until they are, a live key gets service_unavailable, and test mode works as described.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A unique key for this purchase, like a UUID.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainRegisterInput"
              },
              "example": {
                "domain": "example.nl",
                "years": 1,
                "accept_registry_terms": true,
                "max_price_eur": 7.32
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_missing, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_missing",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_missing",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, billing_blocked, insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, company_details_missing, spend_cap_missing.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "company_details_missing",
              "spend_cap_missing"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}": {
      "get": {
        "operationId": "getDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Get a domain",
        "description": "One domain as it is stored: status, nameservers, mail_check, site and next_step. Read-only; POST /domains/{id}/activate checks activation. For a purchased domain, it also includes the registration details and the registrant verification status.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "disconnectDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Disconnect a domain",
        "description": "Takes the site off the domain and deletes its DNS zone (or the www connection). A domain purchased through Hypamoon cannot be disconnected; remove its site instead.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Disconnected, with the previous nameservers to restore.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Update the settings of a domain",
        "description": "Changes a setting of the domain. hsts tells browsers to always open the domain over https: off (default), on (for six months) or on_with_subdomains (the same, for every subdomain too). Once on, browsers that visited the site remember it for about six months and will only open the domain over https, even if you turn it off again. Only choose on_with_subdomains if every subdomain of the domain (like mail or shop) works over https too. It can only be turned on when a site is live on the domain and every host of it has a valid certificate; otherwise the request returns conflict. You can always turn it off. auto_renew is for a domain bought or transferred through Hypamoon: true (default) renews it for a year before it expires, from the project's balance; false lets it expire. For a connected domain it returns conflict. primary chooses the main address: apex (default, example.com) or www (www.example.com). The other address redirects to it permanently (301) with path and query, and canonical links and the sitemap follow it. Only for method nameservers; while the site is live, the new main address needs a valid certificate first, otherwise the request returns conflict.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainUpdateInput"
              },
              "example": {
                "hsts": "on"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The domain, with hsts, primary and registration.auto_renew.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/activate": {
      "post": {
        "operationId": "activateDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Check a domain and attach its site",
        "description": "Checks whether the nameservers (or the CNAME records) are in place, scans the previous DNS again while it waits, and attaches the site once the domain is active.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/site": {
      "put": {
        "operationId": "setDomainSite",
        "tags": [
          "Domains"
        ],
        "summary": "Set the site of a domain",
        "description": "Puts one of the project's sites on the domain and on www, or removes it with null.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainSiteInput"
              },
              "example": {
                "site_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, phone_verification_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "phone_verification_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/phone_verification_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/authcode": {
      "post": {
        "operationId": "getDomainAuthcode",
        "tags": [
          "Domains"
        ],
        "summary": "Get the transfer code of a domain",
        "description": "The transfer (auth) code of a domain bought through Hypamoon, to move it to another registrar. Only the account owner gets it, in the Hypamoon portal (Domains, Show transfer code) after confirming their second factor. A chat (OAuth) and an API key get forbidden; the error names the page in the portal. Each request is logged and the owner receives an email. The domain is unlocked for a transfer for 14 days, then locked again.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transfer code.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/dns-records": {
      "get": {
        "operationId": "listDnsRecords",
        "tags": [
          "Domains"
        ],
        "summary": "List the DNS records",
        "description": "The DNS records of a domain connected through its nameservers. managed_by_hypamoon marks the records that serve the site.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The records.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "setDnsRecord",
        "tags": [
          "Domains"
        ],
        "summary": "Add, change or delete a DNS record",
        "description": "Without record_id a new record (a new A, AAAA or CNAME replaces the A, AAAA or CNAME on that name); with record_id that record is replaced; with delete true it is deleted. The records that serve the site cannot be changed here. On an active domain, a change that replaces or deletes a live record returns 409 confirmation_required with preflight (what changes, what is live there now, mail_affected) and nothing changes; call again with confirm_overwrite true once the user agreed. Every change keeps the previous state for POST /domains/{id}/dns-records/undo.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DnsRecordInput"
              },
              "example": {
                "type": "TXT",
                "name": "@",
                "content": "google-site-verification=abc123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The record (or deleted: true), what it replaced, and how to undo it.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, confirmation_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "confirmation_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/confirmation_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/dns-records/undo": {
      "post": {
        "operationId": "undoDnsChange",
        "tags": [
          "Domains"
        ],
        "summary": "Undo the last DNS change",
        "description": "Reverses the last DNS change made through Hypamoon on the domain, within 30 days: a record set, replaced or deleted, an old record replaced when a subdomain was connected, a cleanup, a zone file import or a restore. Calling it again goes one change further back. Without confirm true nothing changes: the answer is 409 confirmation_required with preflight (the change and what undoing it does). A record that changed again since is not undone blindly (conflict).",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "confirm": {
                    "type": "boolean",
                    "description": "Optional, default false. Without it nothing changes: the answer (confirmation_required) shows which change would be undone and what that does. Show it to the user; after they agree, call again with true."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "confirm": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was undone: removed, restored and skipped records.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, confirmation_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "confirmation_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/confirmation_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/diagnose": {
      "get": {
        "operationId": "diagnoseDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Check a domain and suggest fixes",
        "description": "What is set up on a domain or subdomain right now: the nameservers (at Hypamoon or not), the DNS records here with labels (left by a previous host or not), what answers on the domain, www, the subdomain and the main names (status, server, certificate), email (MX, SPF, DKIM, DMARC, Microsoft 365 or Google Workspace), conflicts and a connection or transfer in progress. Each item in problems has diagnosis, fix (a tool with arguments, or a portal link) and user_message. Read-only, also for a domain that is not connected here. Lookups have short timeouts and never reach private addresses.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "description": "The domain or subdomain, like example.com or portal.example.com.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "description": "Instead of domain: the id of a domain of this project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The diagnosis: nameservers, names, mail, records, problems with a fix each, and user_message.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/zone-file": {
      "post": {
        "operationId": "importZoneFile",
        "tags": [
          "Domains"
        ],
        "summary": "Import a zone file",
        "description": "Imports the records of a BIND zone file (exported from the current DNS provider) into the domain's DNS zone here. NS and SOA of the domain itself and unsupported types are skipped and listed. Also accepts the file itself as text/plain (with ?replace_existing=true). Keeps two restore points: the file (the previous DNS) and the zone as it was just before.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZoneFileInput"
              },
              "example": {
                "zone_file": "$ORIGIN example.com.\n@ 3600 IN MX 10 mail.example.com.\n@ 3600 IN TXT \"v=spf1 include:_spf.example.net ~all\"\n",
                "replace_existing": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was added, removed, skipped and failed, with the restore point to undo it.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/preflight": {
      "get": {
        "operationId": "preflightDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Compare the old and the new DNS",
        "description": "Queries every known record at both the current nameservers and ours (directly, not through a cache) and lists the differences. ready is true only when mail (MX, SPF, DKIM, DMARC), the domain itself and www match. Read-only.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The checks, the blocking ones first, and ready.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/restore": {
      "post": {
        "operationId": "restoreDomainDns",
        "tags": [
          "Domains"
        ],
        "summary": "Restore the DNS records",
        "description": "Sets the domain's DNS zone back to a restore point (by default the newest one of the previous DNS), and returns it as a zone file plus the previous nameservers, so the move can be undone. The zone as it was is kept as a new restore point.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DnsRestoreInput"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "What changed, the zone file and the way back.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/{id}/nameservers": {
      "post": {
        "operationId": "switchDomainNameservers",
        "tags": [
          "Domains"
        ],
        "summary": "Switch the nameservers",
        "description": "For a domain registered or transferred through Hypamoon: point its nameservers to the DNS zone here (refused with confirmation_required and the preflight while it is not ready, unless ignore_preflight), or back to the previous ones.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainNameserversInput"
              },
              "example": {
                "use": "hypamoon"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The domain, with the nameservers now set at the registry.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, confirmation_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "confirmation_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/confirmation_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/domains/transfer": {
      "post": {
        "operationId": "transferDomain",
        "tags": [
          "Domains"
        ],
        "summary": "Transfer a domain to Hypamoon",
        "description": "Moves the registration of a domain the user owns to Hypamoon, with the transfer code of the current registrar. accept_registry_terms must be true and max_price_eur is the amount the user confirmed. dns hypamoon switches the nameservers to the zone here when the transfer completes, so it is refused with confirmation_required (nothing charged) while the preflight is not ready, unless ignore_preflight; keep leaves them as they are. Needs an Idempotency-Key; a key whose attempt failed is never retried. 201 when the transfer completes immediately, 202 while it is in progress (follow it with GET /domains/{id}). In live mode the price is deducted from the balance and refunded if the transfer fails. Transfers in live mode are not open yet: until they are, a live key gets service_unavailable, and test mode works as described.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A unique key for this transfer, like a UUID.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainTransferInput"
              },
              "example": {
                "domain": "example.nl",
                "authcode": "AbC123xyz!",
                "accept_registry_terms": true,
                "max_price_eur": 7.32,
                "dns": "hypamoon"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The transfer completed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            }
          },
          "202": {
            "description": "The transfer was requested and is in progress; transfer.status shows where it stands.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_missing, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_missing",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_missing",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: payment_mandate_missing, billing_blocked, insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "payment_mandate_missing",
              "billing_blocked",
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/payment_mandate_missing",
                            "https://hypamoon.com/docs/errors/billing_blocked",
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress, conflict, confirmation_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "conflict",
              "confirmation_required"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/confirmation_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, company_details_missing, spend_cap_missing.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "company_details_missing",
              "spend_cap_missing"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/spend_cap_missing"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/import": {
      "post": {
        "operationId": "importSite",
        "tags": [
          "Sites"
        ],
        "summary": "Import an existing site",
        "description": "Fetches an existing static site from a domain this project has proven it owns (active here, or proven with the TXT record) and publishes it as a site, through the same checks as POST /sites. Only the same host, respecting robots.txt, within the site limits; links become relative, and forms that need a server lose their action. The response is the same as for POST /sites (or PUT /sites/{id}), with an import report.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SiteImportInput"
              },
              "example": {
                "url": "https://www.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replaced site, with import.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "201": {
            "description": "The new site, with import.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the imported site waits for another owner or builder, with import. Nothing has changed on the live site yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "new_site",
                  "site_id": null,
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached, slug_taken.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached",
              "slug_taken"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/slug_taken"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_limit_reached, slug_reserved, site_too_large, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_limit_reached",
              "slug_reserved",
              "site_too_large",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_limit_reached",
                            "https://hypamoon.com/docs/errors/slug_reserved",
                            "https://hypamoon.com/docs/errors/site_too_large",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/inspect-wordpress": {
      "post": {
        "operationId": "inspectWordpress",
        "tags": [
          "Sites"
        ],
        "summary": "Inspect a WordPress site",
        "description": "Reads what any visitor can see of a WordPress site (its public content API and home page) and reports pages, posts, media, forms, products, the permalink structure, and what cannot move because it is not public (drafts, form entries, users, orders). Read-only, for any public https address; no login is ever used or accepted. Limited per project and per site.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WordpressInspectInput"
              },
              "example": {
                "url": "https://www.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was found, what can move (can_move) and what cannot (cannot_move), with next_step.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/sites/import-wordpress": {
      "post": {
        "operationId": "importWordpress",
        "tags": [
          "Sites"
        ],
        "summary": "Import a WordPress site",
        "description": "Imports the published pages and posts of a WordPress site on a domain this project has proven it owns, as clean pages that keep their addresses, with images within the site limits, a /_redirects file for addresses that change, a collection per form (the form on the new site posts to it), products to the product collection where payments are available, and puts posts that do not fit into a private collection. Publishes through the same checks as POST /sites (or PUT /sites/{id}). The response is the same as for POST /sites, with import (the report) and data (the collections).",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WordpressImportInput"
              },
              "example": {
                "url": "https://www.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated site, with import and data.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "201": {
            "description": "The new site, with import and data.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the imported site waits for another owner or builder. Nothing has changed on the live site yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "new_site",
                  "site_id": null,
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 2,
                  "bytes": 1840,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: version_conflict, plan_limit_reached, slug_taken.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict",
              "plan_limit_reached",
              "slug_taken"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict",
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/slug_taken"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "412": {
            "description": "Precondition Failed: version_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "x-hypamoon-errors": [
              "version_conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/version_conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, site_limit_reached, slug_reserved, site_too_large, site_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "site_limit_reached",
              "slug_reserved",
              "site_too_large",
              "site_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/site_limit_reached",
                            "https://hypamoon.com/docs/errors/slug_reserved",
                            "https://hypamoon.com/docs/errors/site_too_large",
                            "https://hypamoon.com/docs/errors/site_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connect-codes": {
      "post": {
        "operationId": "createWordpressConnectCode",
        "tags": [
          "WordPress"
        ],
        "summary": "Create a code to connect a WordPress site",
        "description": "A one-time code of 8 characters (like ABCD-EF23), valid for 15 minutes and bound to this project. The user enters it in the Hypamoon Connect plugin in WordPress (Tools > Hypamoon). The plugin then sends an inventory of the site (counts, periods and field names, never content), and later only the data the user selected: drafts, private posts, custom post types with ACF fields, form entries, WooCommerce products, orders and customers, users. No WordPress password is ever needed.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "description": "An empty object.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false,
                "description": "An empty object: the code is bound to the project of your key."
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The code, when it expires, and next_step for the agent.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connections": {
      "get": {
        "operationId": "listWordpressConnections",
        "tags": [
          "WordPress"
        ],
        "summary": "List connected WordPress sites",
        "description": "The WordPress sites connected to this project with the Hypamoon Connect plugin, newest first, each with its status, selection, the collections the selection fills and the last export round.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "data: the connections.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connections/{id}": {
      "get": {
        "operationId": "getWordpressConnection",
        "tags": [
          "WordPress"
        ],
        "summary": "Get the inventory of a connected WordPress site",
        "description": "One connected WordPress site with its inventory: per kind of data (key) the count, the period (oldest and newest), the statuses, the field names, whether it holds personal data, the collection it goes to, and the current choice (selected). Personal data (form entries, orders, customers, users, reviews, subscriptions) is off until the user turns it on. next_step says what to do now.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The connection with inventory, selection and next_step.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "revokeWordpressConnection",
        "tags": [
          "WordPress"
        ],
        "summary": "Revoke a WordPress connection",
        "description": "The plugin's token stops working right away. The collections and their documents stay.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connections/{id}/selection": {
      "put": {
        "operationId": "selectWordpressImport",
        "tags": [
          "WordPress"
        ],
        "summary": "Choose what moves from a WordPress site",
        "description": "Per inventory key: on or off, a period (since, until), which statuses for content, and fields to leave out (like phone or ip). Keys you leave out keep their choice. Send version (from the inventory) so you never overwrite a choice the user made meanwhile in WordPress or the portal (409). The plugin shows the same selection and sends only that, in rounds; a later round adds or updates without connecting again.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "true: this kind of data moves in the next round."
                        },
                        "since": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or after this day (like 2024-10-12). null: no limit."
                        },
                        "until": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or before this day. null: no limit."
                        },
                        "statuses": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "enum": [
                              "publish",
                              "draft",
                              "pending",
                              "private",
                              "future"
                            ]
                          },
                          "maxItems": 5,
                          "description": "Only for content (pages, posts, custom post types): which statuses move. null: all of publish, draft, pending, private and future."
                        },
                        "omit_fields": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100
                          },
                          "maxItems": 50,
                          "description": "Field names to leave out everywhere in the record, like phone or ip. null: none."
                        },
                        "include_meta": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "Only for content (pages, posts, custom post types) and users: true also sends all other meta of each item (for content also the hidden _ keys; never passwords, keys or tokens), and turns fields that point to a user or a post (like opdrachtgever_id) into references (relations). Needs Hypamoon Connect 1.2.0. null: only the usual fields."
                        },
                        "names": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 191
                          },
                          "maxItems": 200,
                          "description": "Only for options: the option names to send, from the fields of the options item in the inventory. Names that look like a password, key, token, secret, salt or auth setting are refused. Required to turn options on."
                        }
                      },
                      "required": [
                        "enabled"
                      ],
                      "additionalProperties": false
                    },
                    "propertyNames": {
                      "maxLength": 120
                    },
                    "description": "Per inventory key (like content:post, orders or form_entries:wpforms:12) what moves. Keys you leave out keep their current choice."
                  },
                  "version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The selection version you showed the user (selection.version). If it changed meanwhile, nothing changes (409). Leave it out to change it anyway."
                  },
                  "from_suggestion": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "true: apply the open suggestion of suggest_wordpress_selection (items, if given, go on top of it)."
                  },
                  "include_personal_data": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Only after the user explicitly said yes to the question this route returns (409 with question): true turns on the kinds of data with personal data in this change; false leaves them off and saves the rest."
                  },
                  "confirm_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "With include_personal_data true: the user's answer to the question, word for word. It is kept in the audit log."
                  },
                  "start_round": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "true: the plugin starts sending this selection the next time it checks in (when the admin opens Tools > Hypamoon, or within a few minutes by WP-cron)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "items": {
                  "orders": {
                    "enabled": true,
                    "since": "2024-10-12",
                    "omit_fields": [
                      "phone"
                    ]
                  }
                },
                "version": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new selection, with its version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connections/{id}/suggestion": {
      "get": {
        "operationId": "getWordpressSuggestion",
        "tags": [
          "WordPress"
        ],
        "summary": "Read the compact inventory and the open suggestion",
        "description": "The inventory in its smallest form, structure only: per kind of data the group, count, period, a few field names, whether it holds personal data and why, the fields that point to other records (like opdrachtgever_id) and what is selected now. Never values. Also the open suggestion, if there is one.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "inventory (compact), suggestion and selection_version.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "put": {
        "operationId": "suggestWordpressSelection",
        "tags": [
          "WordPress"
        ],
        "summary": "Suggest a selection for a WordPress site",
        "description": "Stores a suggestion, per inventory key on or off with a short reason, based on what the user wants. It is never applied: the user reviews and applies it in WordPress (Tools > Hypamoon > Move data), or says yes in the chat, after which PUT /selection with from_suggestion applies it. Personal data is marked and never ticked automatically. A new suggestion replaces the open one.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "goal": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 300,
                    "description": "What the user wants, in their words, like: move my hours portal, or only my pages and blog."
                  },
                  "items": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "true: this kind of data moves in the next round."
                        },
                        "since": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or after this day (like 2024-10-12). null: no limit."
                        },
                        "until": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or before this day. null: no limit."
                        },
                        "statuses": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "enum": [
                              "publish",
                              "draft",
                              "pending",
                              "private",
                              "future"
                            ]
                          },
                          "maxItems": 5,
                          "description": "Only for content (pages, posts, custom post types): which statuses move. null: all of publish, draft, pending, private and future."
                        },
                        "omit_fields": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100
                          },
                          "maxItems": 50,
                          "description": "Field names to leave out everywhere in the record, like phone or ip. null: none."
                        },
                        "include_meta": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "Only for content (pages, posts, custom post types) and users: true also sends all other meta of each item (for content also the hidden _ keys; never passwords, keys or tokens), and turns fields that point to a user or a post (like opdrachtgever_id) into references (relations). Needs Hypamoon Connect 1.2.0. null: only the usual fields."
                        },
                        "names": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 191
                          },
                          "maxItems": 200,
                          "description": "Only for options: the option names to send, from the fields of the options item in the inventory. Names that look like a password, key, token, secret, salt or auth setting are refused. Required to turn options on."
                        },
                        "reason": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 200,
                          "description": "Why, in a few words for the user, like: needed for the hour history, linked to users via opdrachtgever_id."
                        }
                      },
                      "required": [
                        "enabled",
                        "reason"
                      ],
                      "additionalProperties": false
                    },
                    "propertyNames": {
                      "maxLength": 120
                    },
                    "description": "Per inventory key what you suggest: enabled true or false, a reason, and optionally since, until, statuses, omit_fields, include_meta and names (options only by name). Keys you leave out stay as they are."
                  }
                },
                "required": [
                  "goal",
                  "items"
                ],
                "additionalProperties": false
              },
              "example": {
                "goal": "Move my hours portal",
                "items": {
                  "table:uren": {
                    "enabled": true,
                    "reason": "The hour history, linked to users via opdrachtgever_id."
                  },
                  "content:page": {
                    "enabled": false,
                    "reason": "The pages stay on the WordPress site."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The suggestion with its differences from the current selection, a summary and next_step.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connections/{id}/embeds": {
      "get": {
        "operationId": "getWordpressEmbeds",
        "tags": [
          "WordPress"
        ],
        "summary": "Read the chat and forms on a WordPress site",
        "description": "What the Hypamoon Connect plugin puts on the connected WordPress site: the chat window (on or off, colour, position, title, welcome) and the forms that post to collections with public_write, each with its fields and its shortcode. public_token is the public token the site uses for both; problems lists what blocks it. Per site, not per connection: a new connection to the same site keeps these settings.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The chat, the forms, the version and problems.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "put": {
        "operationId": "setWordpressEmbeds",
        "tags": [
          "WordPress"
        ],
        "summary": "Put the chat and forms on a WordPress site",
        "description": "Turns the chat window on or off on the connected WordPress site and chooses the forms the plugin offers (a block and the shortcode [hypamoon_form id=...]). Forms need a collection with public_write; a field named email becomes the Reply-To of the notification email. Makes sure a public token allows the site's origin: the chosen one gets the origin added, or an existing one is used, or a new one is created. The plugin picks the settings up when the admin opens Tools > Hypamoon or presses Refresh. Send version to avoid overwriting a change made meanwhile (409).",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the WordPress connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chat": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "Show the chat window on every page of the WordPress site."
                      },
                      "chat_token_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "Optional. The public chat token to use (from list_chat_widgets). Without it, a token that already allows this site, or a new one."
                      },
                      "color": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$",
                        "description": "The colour of the chat button, like #0f766e."
                      },
                      "position": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "left",
                          "right",
                          null
                        ],
                        "description": "Where the chat button sits. Default: right."
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60,
                        "description": "The title of the chat window. Default: the business name."
                      },
                      "welcome": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300,
                        "description": "The first message in the chat window. Default: the assistant's greeting."
                      }
                    },
                    "required": [
                      "enabled"
                    ],
                    "additionalProperties": false,
                    "description": "The chat window on the site. Leave it out to keep it as it is."
                  },
                  "forms": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "object",
                      "properties": {
                        "collection": {
                          "type": "string",
                          "pattern": "^[a-z][a-z0-9_]{1,39}$",
                          "description": "The collection the form posts to. It needs access.public_write."
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 120,
                          "description": "An optional heading above the form."
                        },
                        "fields": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "pattern": "^[A-Za-z_][A-Za-z0-9_-]{0,39}$",
                                "description": "The key in the document, like email or party_size."
                              },
                              "label": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 120,
                                "description": "The visible label. Default: the name."
                              },
                              "type": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "text",
                                  "email",
                                  "tel",
                                  "textarea",
                                  "number",
                                  "date",
                                  "select",
                                  "checkbox",
                                  null
                                ],
                                "description": "text, email, tel, textarea, number, date, select or checkbox. Default: text."
                              },
                              "required": {
                                "type": [
                                  "boolean",
                                  "null"
                                ]
                              },
                              "options": {
                                "type": [
                                  "array",
                                  "null"
                                ],
                                "items": {
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "maxItems": 50,
                                "description": "The choices of a select."
                              }
                            },
                            "required": [
                              "name"
                            ],
                            "additionalProperties": false
                          },
                          "minItems": 1,
                          "maxItems": 30,
                          "description": "The inputs. Without them: from the collection's schema, or name, email and message."
                        },
                        "submit_label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 60,
                          "description": "The text of the button. Default: Send."
                        },
                        "success_message": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 300,
                          "description": "Shown after sending. Default: Thanks, we received your message."
                        },
                        "remove": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "true: no longer offer this form in WordPress."
                        }
                      },
                      "required": [
                        "collection"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 20,
                    "description": "Forms to offer or change. Forms you leave out keep their settings."
                  },
                  "version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The version you showed the user. If it changed meanwhile, nothing changes (409). Leave it out to change it anyway."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "chat": {
                  "enabled": true,
                  "color": "#0f766e"
                },
                "forms": [
                  {
                    "collection": "contact",
                    "title": "Contact us"
                  }
                ],
                "version": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new settings, as GET returns them.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/wordpress/connect": {
      "post": {
        "operationId": "connectWordpressPlugin",
        "tags": [
          "WordPress"
        ],
        "summary": "Connect the plugin with a code (plugin)",
        "description": "Called by the Hypamoon Connect plugin, not by you. Exchanges the one-time code for the plugin's own token (hm_wp_...), bound to this project and this WordPress site. Limited per IP address. The token is valid for 30 days and can be revoked. Answers with the token (shown once), the connection id, the project, and the limits: 200 records and 3 MB per batch, 50000 records and 200 MB per round.",
        "x-hypamoon-auth": "wordpress_connect_code",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 20,
                    "description": "The one-time code of 8 characters, like ABCD-EF23 (dashes, spaces and case do not matter)."
                  },
                  "site_url": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 400,
                    "description": "The address of the WordPress site (home_url), like https://www.example.com."
                  },
                  "site_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "The name of the WordPress site."
                  },
                  "wordpress_version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "The WordPress version, like 6.6.2."
                  },
                  "plugin_version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "The version of Hypamoon Connect, like 1.0.0."
                  }
                },
                "required": [
                  "code",
                  "site_url"
                ]
              },
              "example": {
                "code": "ABCD-EF23",
                "site_url": "https://www.example.com",
                "site_name": "Example",
                "wordpress_version": "6.6.2",
                "plugin_version": "1.0.0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token, the connection, the project and the limits.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/connection": {
      "get": {
        "operationId": "getWordpressPluginConnection",
        "tags": [
          "WordPress"
        ],
        "summary": "Read the connection (plugin)",
        "description": "Called by the plugin: the project, the site, when the token expires, the selection and the last export round.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The connection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "disconnectWordpressPlugin",
        "tags": [
          "WordPress"
        ],
        "summary": "Disconnect (plugin)",
        "description": "Called by the plugin when the admin disconnects: the token stops working. The collections stay.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/connection/inventory": {
      "put": {
        "operationId": "putWordpressInventory",
        "tags": [
          "WordPress"
        ],
        "summary": "Send the inventory (plugin)",
        "description": "Called by the plugin right after connecting and on Refresh: per kind of data the count, period, statuses and field names (ACF field groups, form fields), never content. Form entries, orders, customers, users, reviews and subscriptions are always marked as personal data. Answers with the selection, with defaults for new keys (content on, personal data off).",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "pattern": "^[a-z_]{2,30}(:[A-Za-z0-9_.-]{1,80}){0,2}$"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "content",
                            "media",
                            "menus",
                            "terms",
                            "acf_field_groups",
                            "acf_options",
                            "form_entries",
                            "products",
                            "orders",
                            "customers",
                            "users",
                            "coupons",
                            "shipping",
                            "tax",
                            "reviews",
                            "subscriptions",
                            "table",
                            "options"
                          ]
                        },
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "count": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100000000
                        },
                        "oldest": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 40
                        },
                        "newest": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 40
                        },
                        "statuses": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "propertyNames": {
                            "maxLength": 40
                          }
                        },
                        "personal_data": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "fields": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 200
                              },
                              "label": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 200
                              },
                              "type": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 60
                              }
                            },
                            "required": [
                              "name"
                            ]
                          },
                          "maxItems": 500
                        },
                        "form": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "plugin": {
                              "type": "string",
                              "enum": [
                                "contact_form_7",
                                "wpforms",
                                "gravity_forms"
                              ]
                            },
                            "id": {
                              "anyOf": [
                                {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 80
                                },
                                {
                                  "type": "integer"
                                }
                              ]
                            },
                            "title": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "plugin",
                            "id"
                          ]
                        },
                        "post_type": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 40
                        },
                        "public": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "primary_key": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 64
                        },
                        "movable": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 500
                        }
                      },
                      "required": [
                        "key",
                        "type",
                        "label",
                        "count"
                      ]
                    },
                    "maxItems": 500,
                    "description": "One item per kind of data: counts, period and field names, never content."
                  }
                },
                "required": [
                  "items"
                ]
              },
              "example": {
                "items": [
                  {
                    "key": "content:post",
                    "type": "content",
                    "label": "Posts",
                    "count": 120,
                    "oldest": "2019-03-01T10:00:00Z",
                    "newest": "2026-10-01T09:00:00.000Z",
                    "statuses": {
                      "publish": 100,
                      "draft": 20
                    },
                    "post_type": "post"
                  },
                  {
                    "key": "orders",
                    "type": "orders",
                    "label": "Orders",
                    "count": 830,
                    "oldest": "2021-01-04T09:00:00Z",
                    "newest": "2026-10-01T09:00:00.000Z"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "inventory_at and the selection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/connection/selection": {
      "get": {
        "operationId": "getWordpressPluginSelection",
        "tags": [
          "WordPress"
        ],
        "summary": "Pull the selection (plugin)",
        "description": "Called by the plugin: what the user chose (in a chat with their AI assistant, in the portal or in the plugin), with its version.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The selection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "putWordpressPluginSelection",
        "tags": [
          "WordPress"
        ],
        "summary": "Change the selection (plugin)",
        "description": "Called by the plugin when the admin changes the checkboxes in WordPress. Send the version it showed; a newer one gives 409.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "true: this kind of data moves in the next round."
                        },
                        "since": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or after this day (like 2024-10-12). null: no limit."
                        },
                        "until": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Only records dated on or before this day. null: no limit."
                        },
                        "statuses": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "enum": [
                              "publish",
                              "draft",
                              "pending",
                              "private",
                              "future"
                            ]
                          },
                          "maxItems": 5,
                          "description": "Only for content (pages, posts, custom post types): which statuses move. null: all of publish, draft, pending, private and future."
                        },
                        "omit_fields": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100
                          },
                          "maxItems": 50,
                          "description": "Field names to leave out everywhere in the record, like phone or ip. null: none."
                        },
                        "include_meta": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "Only for content (pages, posts, custom post types) and users: true also sends all other meta of each item (for content also the hidden _ keys; never passwords, keys or tokens), and turns fields that point to a user or a post (like opdrachtgever_id) into references (relations). Needs Hypamoon Connect 1.2.0. null: only the usual fields."
                        },
                        "names": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 191
                          },
                          "maxItems": 200,
                          "description": "Only for options: the option names to send, from the fields of the options item in the inventory. Names that look like a password, key, token, secret, salt or auth setting are refused. Required to turn options on."
                        }
                      },
                      "required": [
                        "enabled"
                      ],
                      "additionalProperties": false
                    },
                    "propertyNames": {
                      "maxLength": 120
                    },
                    "description": "Per inventory key (like content:post, orders or form_entries:wpforms:12) what moves. Keys you leave out keep their current choice."
                  },
                  "version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The selection version you showed the user (selection.version). If it changed meanwhile, nothing changes (409). Leave it out to change it anyway."
                  },
                  "from_suggestion": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "true: apply the open suggestion of suggest_wordpress_selection (items, if given, go on top of it)."
                  },
                  "include_personal_data": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Only after the user explicitly said yes to the question this route returns (409 with question): true turns on the kinds of data with personal data in this change; false leaves them off and saves the rest."
                  },
                  "confirm_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "With include_personal_data true: the user's answer to the question, word for word. It is kept in the audit log."
                  },
                  "start_round": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "true: the plugin starts sending this selection the next time it checks in (when the admin opens Tools > Hypamoon, or within a few minutes by WP-cron)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "items": {
                  "users": {
                    "enabled": true,
                    "omit_fields": null
                  }
                },
                "version": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new selection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/connection/suggestion": {
      "delete": {
        "operationId": "closeWordpressPluginSuggestion",
        "tags": [
          "WordPress"
        ],
        "summary": "Close the suggestion of the chat (plugin)",
        "description": "Called by the plugin when the admin applied and saved the suggestion (outcome=applied) or dismissed it (outcome=dismissed). The suggestion itself comes with GET /wordpress/connection.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "parameters": [
          {
            "name": "outcome",
            "in": "query",
            "description": "applied or dismissed.",
            "schema": {
              "type": "string",
              "enum": [
                "applied",
                "dismissed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The status of the suggestion.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/connection/embeds": {
      "get": {
        "operationId": "getWordpressPluginEmbeds",
        "tags": [
          "WordPress"
        ],
        "summary": "Pull the chat and forms for the site (plugin)",
        "description": "Called by the plugin when the admin opens its screen or presses Refresh: the chat window and the forms for this site, with the public token. The plugin stores them; visitors talk to the public widget and form endpoints, never with the plugin's token.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The chat, the forms, the version and problems.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "operationId": "putWordpressPluginEmbeds",
        "tags": [
          "WordPress"
        ],
        "summary": "Turn the chat on or off (plugin)",
        "description": "Called by the plugin when the admin turns the chat window on or off, or sets up the forms. Makes sure a public token allows the site. Send the version it showed; a newer one gives 409.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chat": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "enabled"
                    ],
                    "additionalProperties": false
                  },
                  "version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "chat": {
                  "enabled": true
                },
                "version": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new settings.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/exports": {
      "post": {
        "operationId": "startWordpressExport",
        "tags": [
          "WordPress"
        ],
        "summary": "Start an export round (plugin)",
        "description": "Called by the plugin: a round sends only what is selected. Send the selection version the plugin shows; a newer one gives 409. A running round of the same connection stops.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "selection_version": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The selection version the plugin pulled and shows. A newer one gives 409: pull again."
                  }
                },
                "required": [
                  "selection_version"
                ]
              },
              "example": {
                "selection_version": 2
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The round: export_id, status and the selection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/exports/{id}/batches": {
      "post": {
        "operationId": "sendWordpressBatch",
        "tags": [
          "WordPress"
        ],
        "summary": "Send a batch (plugin)",
        "description": "Called by the plugin: at most 200 records and 3 MB per batch, 50000 records and 200 MB per round, 120 requests per minute. Every record has wp_id; the same record in a later round updates its document. A batch key that was already received changes nothing (duplicate: true). The selection is applied again here: what is not selected, outside the period or in omit_fields is not stored. Passwords, hashes and tokens are always removed.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the export round.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "batch": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "A key for this batch, unique and deterministic within the round, like content:post:after:0. The same key again changes nothing."
                  },
                  "key": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120,
                    "description": "The inventory key, like content:post, orders or form_entries:wpforms:12."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "content",
                      "media",
                      "menus",
                      "terms",
                      "acf_field_groups",
                      "acf_options",
                      "form_entries",
                      "products",
                      "orders",
                      "customers",
                      "users",
                      "coupons",
                      "shipping",
                      "tax",
                      "reviews",
                      "subscriptions",
                      "table",
                      "options"
                    ],
                    "description": "The kind of data, the first part of key."
                  },
                  "form": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "plugin": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "id": {
                        "anyOf": [
                          {
                            "type": "string",
                            "maxLength": 80
                          },
                          {
                            "type": "integer"
                          }
                        ]
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      }
                    },
                    "required": [
                      "plugin",
                      "id"
                    ],
                    "description": "Only for form_entries: the form."
                  },
                  "records": {
                    "type": "array",
                    "items": {},
                    "maxItems": 200,
                    "description": "At most 200 records, each with wp_id."
                  }
                },
                "required": [
                  "batch",
                  "key",
                  "type",
                  "records"
                ]
              },
              "example": {
                "batch": "content:post:after:0",
                "key": "content:post",
                "type": "content",
                "records": [
                  {
                    "wp_id": 12,
                    "post_type": "post",
                    "status": "draft",
                    "title": "Spring menu",
                    "content_html": "<p>...</p>"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was stored: received, created, updated, skipped (with reasons), the collections and the totals of the round.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, collection_limit_reached, document_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "collection_limit_reached",
              "document_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/collection_limit_reached",
                            "https://hypamoon.com/docs/errors/document_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wordpress/exports/{id}/finish": {
      "post": {
        "operationId": "finishWordpressExport",
        "tags": [
          "WordPress"
        ],
        "summary": "Finish an export round (plugin)",
        "description": "Called by the plugin at the end of a round: the collections with their document counts and next_step for the admin.",
        "x-hypamoon-auth": "wordpress_token",
        "security": [
          {
            "WordPressToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the export round.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The round's result.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/collections": {
      "get": {
        "operationId": "listCollections",
        "tags": [
          "Data"
        ],
        "summary": "List the collections",
        "description": "The project's collections, newest first, each with its access, triggers, schema and document_count. Deleted collections are not listed.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The collections.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionList"
                },
                "example": {
                  "data": [
                    {
                      "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                      "name": "contact",
                      "description": "The contact form on the website.",
                      "access": {
                        "public_write": true,
                        "public_read": false,
                        "assistant_read": false
                      },
                      "triggers": {
                        "email_on_insert": true,
                        "webhook_on_insert": false
                      },
                      "email_routing": null,
                      "schema": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100
                          },
                          "email": {
                            "type": "string",
                            "format": "email"
                          },
                          "message": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 5000
                          }
                        },
                        "required": [
                          "name",
                          "email",
                          "message"
                        ]
                      },
                      "personal_data": false,
                      "document_count": 1,
                      "created_at": "2026-10-01T09:00:00.000Z",
                      "updated_at": "2026-10-01T09:00:00.000Z",
                      "test": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createCollection",
        "tags": [
          "Data"
        ],
        "summary": "Create a collection",
        "description": "A collection holds JSON documents: the entries of a contact form, sign-ups, reservations, or facts for the assistant. Free, including in test mode. access decides who may use it besides your API key: private (all false, the default); public_write for a form on your website (with the public token, a bot check and limits); public_read to show the newest documents on a page; assistant_read to give the assistant the newest documents added through the API. triggers: email_on_insert emails every new document to the project's billing email address (live only, at most 50 emails per collection per day; beyond that, the document is still stored and the webhook is still sent), webhook_on_insert sends document.created. schema is an optional JSON Schema that every document must match. At most 20 collections per project on Free, 50 on Online, 100 on Reachable, 200 on Business.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CollectionInput"
              },
              "example": {
                "name": "contact",
                "description": "The contact form on the website.",
                "access": {
                  "public_write": true
                },
                "triggers": {
                  "email_on_insert": true
                },
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 100
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "message": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 5000
                    }
                  },
                  "required": [
                    "name",
                    "email",
                    "message"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new collection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Collection"
                },
                "example": {
                  "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "name": "contact",
                  "description": "The contact form on the website.",
                  "access": {
                    "public_write": true,
                    "public_read": false,
                    "assistant_read": false
                  },
                  "triggers": {
                    "email_on_insert": true,
                    "webhook_on_insert": false
                  },
                  "email_routing": null,
                  "schema": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "message": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      }
                    },
                    "required": [
                      "name",
                      "email",
                      "message"
                    ]
                  },
                  "personal_data": false,
                  "document_count": 0,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Conflict: collection_name_taken.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "collection_name_taken"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/collection_name_taken"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, collection_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "collection_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/collection_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/collections/{id}": {
      "get": {
        "operationId": "getCollection",
        "tags": [
          "Data"
        ],
        "summary": "Get a collection",
        "description": "One collection with its access, triggers, schema and document_count. The path takes the id or the name.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The collection.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Collection"
                },
                "example": {
                  "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "name": "contact",
                  "description": "The contact form on the website.",
                  "access": {
                    "public_write": true,
                    "public_read": false,
                    "assistant_read": false
                  },
                  "triggers": {
                    "email_on_insert": true,
                    "webhook_on_insert": false
                  },
                  "email_routing": null,
                  "schema": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "message": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      }
                    },
                    "required": [
                      "name",
                      "email",
                      "message"
                    ]
                  },
                  "personal_data": false,
                  "document_count": 1,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "test": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateCollection",
        "tags": [
          "Data"
        ],
        "summary": "Change a collection",
        "description": "Changes the name, description, access, triggers or schema. Fields you leave out stay as they are, including inside access and triggers. A new schema applies to new and changed documents; existing documents are not checked again. A new name breaks a form that posts to the old one.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CollectionUpdate"
              },
              "example": {
                "access": {
                  "public_write": true
                },
                "triggers": {
                  "webhook_on_insert": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The collection as it is now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Collection"
                },
                "example": {
                  "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "name": "contact",
                  "description": "The contact form on the website.",
                  "access": {
                    "public_write": true,
                    "public_read": false,
                    "assistant_read": false
                  },
                  "triggers": {
                    "email_on_insert": true,
                    "webhook_on_insert": false
                  },
                  "email_routing": null,
                  "schema": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "message": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      }
                    },
                    "required": [
                      "name",
                      "email",
                      "message"
                    ]
                  },
                  "personal_data": false,
                  "document_count": 1,
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "updated_at": "2026-10-01T09:00:00.000Z",
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: collection_name_taken.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "collection_name_taken"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/collection_name_taken"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteCollection",
        "tags": [
          "Data"
        ],
        "summary": "Delete a collection",
        "description": "Deletes the collection: its documents can no longer be reached, a form that posts to it receives 404, and the name becomes available again. Deleting it again returns 204 as well.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/collections/{id}/documents": {
      "get": {
        "operationId": "listDocuments",
        "tags": [
          "Data"
        ],
        "summary": "List the documents",
        "description": "The documents of a collection, newest first. Follow next_cursor while has_more is true, or pass before to start at a point in time. filter matches top-level fields exactly: filter={\"status\":\"new\"} returns only documents whose status is new.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Documents per page, default 25. A value above 100 is capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25
            },
            "example": 25
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "Only documents added before this moment, in ISO 8601.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "filter",
            "in": "query",
            "description": "A JSON object of top-level fields and the exact value they must have (string, number, true, false or null), like {\"status\":\"new\"}. URL-encode it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentList"
                },
                "example": {
                  "data": [
                    {
                      "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                      "collection_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                      "data": {
                        "name": "Anna de Vries",
                        "email": "anna@example.com",
                        "message": "Can you come and look at a leaking tap on Friday?"
                      },
                      "source": "public",
                      "created_at": "2026-10-01T09:30:00.000Z",
                      "updated_at": "2026-10-01T09:30:00.000Z",
                      "test": false
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createDocument",
        "tags": [
          "Data"
        ],
        "summary": "Add a document",
        "description": "Adds a JSON document of at most 64 kB to the collection; with a schema on the collection, it must match. At most 10000 documents per collection. The collection's triggers fire just as for a form: document.created if webhook_on_insert is on (including in test mode), and an email if email_on_insert is on (not in test mode).",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentInput"
              },
              "example": {
                "data": {
                  "name": "Anna de Vries",
                  "email": "anna@example.com",
                  "message": "Can you come and look at a leaking tap on Friday?"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new document.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                },
                "example": {
                  "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                  "collection_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "data": {
                    "name": "Anna de Vries",
                    "email": "anna@example.com",
                    "message": "Can you come and look at a leaking tap on Friday?"
                  },
                  "source": "api",
                  "created_at": "2026-10-01T09:30:00.000Z",
                  "updated_at": "2026-10-01T09:30:00.000Z",
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, schema_mismatch, document_too_large, document_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "schema_mismatch",
              "document_too_large",
              "document_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/schema_mismatch",
                            "https://hypamoon.com/docs/errors/document_too_large",
                            "https://hypamoon.com/docs/errors/document_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/collections/{id}/documents/{docId}": {
      "get": {
        "operationId": "getDocument",
        "tags": [
          "Data"
        ],
        "summary": "Get a document",
        "description": "One document from the collection.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "docId",
            "in": "path",
            "required": true,
            "description": "The id of the document.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                },
                "example": {
                  "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                  "collection_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "data": {
                    "name": "Anna de Vries",
                    "email": "anna@example.com",
                    "message": "Can you come and look at a leaking tap on Friday?"
                  },
                  "source": "public",
                  "created_at": "2026-10-01T09:30:00.000Z",
                  "updated_at": "2026-10-01T09:30:00.000Z",
                  "test": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateDocument",
        "tags": [
          "Data"
        ],
        "summary": "Change a document",
        "description": "Send data to replace the whole document, or merge to change some fields (JSON Merge Patch, RFC 7396: a field you send replaces that field, null removes it, an object is merged field by field). Not both. The result must match the schema of the collection. Triggers do not fire on a change.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "docId",
            "in": "path",
            "required": true,
            "description": "The id of the document.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentUpdate"
              },
              "example": {
                "merge": {
                  "status": "answered"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document as it is now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                },
                "example": {
                  "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                  "collection_id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                  "data": {
                    "name": "Anna de Vries",
                    "email": "anna@example.com",
                    "message": "Can you come and look at a leaking tap on Friday?",
                    "status": "answered"
                  },
                  "source": "public",
                  "created_at": "2026-10-01T09:30:00.000Z",
                  "updated_at": "2026-10-01T09:30:00.000Z",
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, schema_mismatch, document_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "schema_mismatch",
              "document_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/schema_mismatch",
                            "https://hypamoon.com/docs/errors/document_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteDocument",
        "tags": [
          "Data"
        ],
        "summary": "Delete a document",
        "description": "Deletes the document immediately and permanently. A second delete returns 404.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the collection, or its name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "docId",
            "in": "path",
            "required": true,
            "description": "The id of the document.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/public/config": {
      "get": {
        "operationId": "getPublicChatConfig",
        "tags": [
          "Chat widget"
        ],
        "summary": "Get the widget settings (public)",
        "description": "Called by the chat widget in the visitor's browser, with the public chat token in ?token= and the Origin header of the page. No API key. Returns what the widget shows before a conversation starts: the project name, the greeting, the language and whether a bot check is needed. Every /public route allows CORS only for an allowed origin (with an OPTIONS preflight), and any reason that concerns you rather than the visitor (balance, verification, the daily spending limit) returns 503 chat_unavailable, without details; the real reason is in the portal. All /public requests combined: at most 60 per minute per IP address.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The settings of the widget.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicChatConfig"
                },
                "example": {
                  "name": "Acme Plumbing",
                  "greeting": "You are chatting with the digital assistant of Acme Plumbing. How can I help you?",
                  "language": "en",
                  "languages": [
                    "en"
                  ],
                  "mode": "live",
                  "turnstile": true,
                  "max_message_length": 2000,
                  "max_turns": 30
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/public/conversations": {
      "post": {
        "operationId": "startPublicConversation",
        "tags": [
          "Chat widget"
        ],
        "summary": "Start a conversation from the widget (public)",
        "description": "Called by the chat widget in the visitor's browser, with the public chat token in ?token= and the Origin header of the page. No API key. Starts a conversation (channel widget) with the assistant and returns the greeting and conversation_token: the secret of this conversation for this visitor, valid for 6 hours. Every later request for it needs that secret as X-Conversation-Token. With turnstile on (the default) send turnstile_token from the bot check; the widget shows that check in a frame on our host and does all of this for you. Limits: 3 new conversations per minute and 20 per hour per IP address, 30 per minute and 300 per hour per token, the token's daily limit (public_chat_limit_reached) and, in live mode, the project's daily spending limit. Your backend can read the conversation with GET /conversations/{id} and receives conversation.summary.ready like any other chat.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "An empty body is fine when turnstile is off for the token.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicConversationInput"
              },
              "example": {
                "turnstile_token": "0.zXnA8v1rT3...from-the-bot-check"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The conversation with the greeting and its secret.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicConversationStart"
                },
                "example": {
                  "conversation": {
                    "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                    "channel": "widget",
                    "status": "open",
                    "language": "en",
                    "turns": 0,
                    "max_turns": 30,
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "last_message_at": "2026-10-01T09:00:00.000Z",
                    "closed_at": null,
                    "close_reason": null,
                    "test": false
                  },
                  "messages": [
                    {
                      "id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
                      "role": "assistant",
                      "text": "You are chatting with the digital assistant of Acme Plumbing. How can I help you?",
                      "created_at": "2026-10-01T09:00:00.000Z"
                    }
                  ],
                  "conversation_token": "v1.2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f.1b2c3d4e-5f60-4718-8a9b-0c1d2e3f4a5b.1790866800.Qm9vZ3N0ZWVuLXZvb3JiZWVsZC1oYW5kdGVrZW5pbmc",
                  "expires_at": "2026-10-01T15:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Payment Required: test_free_replies_used.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "test_free_replies_used"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/test_free_replies_used"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed, turnstile_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed",
              "turnstile_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed",
                            "https://hypamoon.com/docs/errors/turnstile_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, public_chat_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "public_chat_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/public_chat_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/public/conversations/{id}": {
      "get": {
        "operationId": "getPublicConversation",
        "tags": [
          "Chat widget"
        ],
        "summary": "Get the visitor's conversation (public)",
        "description": "Called by the chat widget to restore the conversation after a page load. Needs the public chat token in ?token=, the Origin header and X-Conversation-Token. Returns only the messages and the state: no metadata, customer details or summary. A conversation from another token, or one started with your API key, is never reachable here.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the conversation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Conversation-Token",
            "in": "header",
            "required": true,
            "description": "The conversation_token from POST /public/conversations. Only the visitor who started the conversation has it.",
            "schema": {
              "type": "string",
              "maxLength": 300
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation with its messages.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicConversationDetail"
                },
                "example": {
                  "conversation": {
                    "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                    "channel": "widget",
                    "status": "open",
                    "language": "en",
                    "turns": 1,
                    "max_turns": 30,
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "last_message_at": "2026-10-01T09:00:22.000Z",
                    "closed_at": null,
                    "close_reason": null,
                    "test": false
                  },
                  "messages": [
                    {
                      "id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
                      "role": "assistant",
                      "text": "You are chatting with the digital assistant of Acme Plumbing. How can I help you?",
                      "created_at": "2026-10-01T09:00:00.000Z"
                    },
                    {
                      "id": "5f6a7b8c-9d0e-4f1a-b2c3-d4e5f6a7b8c9",
                      "role": "customer",
                      "text": "My kitchen tap is leaking. Can someone come today?",
                      "created_at": "2026-10-01T09:00:20.000Z"
                    },
                    {
                      "id": "6a7b8c9d-0e1f-4a2b-83c4-e5f6a7b8c9d0",
                      "role": "assistant",
                      "text": "I'm sorry about the leak. I can't plan a visit myself, but I'll pass it on: on which number can our team call you back within the hour?",
                      "created_at": "2026-10-01T09:00:22.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked, invalid_conversation_token.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked",
              "invalid_conversation_token"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked",
                            "https://hypamoon.com/docs/errors/invalid_conversation_token"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/public/conversations/{id}/messages": {
      "post": {
        "operationId": "sendPublicChatMessage",
        "tags": [
          "Chat widget"
        ],
        "summary": "Send a visitor message and get the reply (public)",
        "description": "Called by the chat widget for every message the visitor types. Needs the public chat token in ?token=, the Origin header and X-Conversation-Token. Works like POST /conversations/{id}/messages: the same assistant, the same checks, and with `Accept: text/event-stream` the same Server-Sent Events (delta, then done or error). Limits: 2000 characters per message, 30 turns per conversation, 6 messages per minute per conversation and 20 per minute per IP address. In live mode, every reply is billed per use from your prepaid balance and counts toward the public chat's daily spending limit (counted at a safe 3 cents a reply); beyond that limit, the request returns public_chat_limit_reached.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the conversation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Conversation-Token",
            "in": "header",
            "required": true,
            "description": "The conversation_token from POST /public/conversations. Only the visitor who started the conversation has it.",
            "schema": {
              "type": "string",
              "maxLength": 300
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "description": "text/event-stream streams the reply as Server-Sent Events; anything else returns a single JSON response.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/event-stream"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatMessageInput"
              },
              "example": {
                "text": "My kitchen tap is leaking. Can someone come today?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The visitor's message and the assistant's reply.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicChatTurn"
                },
                "example": {
                  "message": {
                    "id": "5f6a7b8c-9d0e-4f1a-b2c3-d4e5f6a7b8c9",
                    "role": "customer",
                    "text": "My kitchen tap is leaking. Can someone come today?",
                    "created_at": "2026-10-01T09:00:20.000Z"
                  },
                  "reply": {
                    "id": "6a7b8c9d-0e1f-4a2b-83c4-e5f6a7b8c9d0",
                    "role": "assistant",
                    "text": "I'm sorry about the leak. I can't plan a visit myself, but I'll pass it on: on which number can our team call you back within the hour?",
                    "created_at": "2026-10-01T09:00:22.000Z"
                  },
                  "conversation": {
                    "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                    "channel": "widget",
                    "status": "open",
                    "language": "en",
                    "turns": 1,
                    "max_turns": 30,
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "last_message_at": "2026-10-01T09:00:20.000Z",
                    "closed_at": null,
                    "close_reason": null,
                    "test": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked, invalid_conversation_token.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked",
              "invalid_conversation_token"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked",
                            "https://hypamoon.com/docs/errors/invalid_conversation_token"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Payment Required: test_free_replies_used.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "test_free_replies_used"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/test_free_replies_used"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conversation_closed, conversation_busy, conversation_turn_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conversation_closed",
              "conversation_busy",
              "conversation_turn_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conversation_closed",
                            "https://hypamoon.com/docs/errors/conversation_busy",
                            "https://hypamoon.com/docs/errors/conversation_turn_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, public_chat_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "public_chat_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/public_chat_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/public/challenge": {
      "get": {
        "operationId": "getPublicChallenge",
        "tags": [
          "Chat widget"
        ],
        "summary": "The bot check page for the widget (public)",
        "description": "An HTML page with the bot check, which the chat widget shows in a frame; the widget handles this for you. It sends the bot-check token with postMessage only to `origin`, which must be an allowed origin of the token, and only that origin may frame it. Query: token (the public chat token), origin (the origin of the page with the widget) and lang (en, nl, de, fr or es). An unknown or revoked token returns a plain 404, and an origin that is not allowed returns a plain 403.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "parameters": [
          {
            "name": "origin",
            "in": "query",
            "required": true,
            "description": "The origin of the page with the widget, like https://www.example.com.",
            "schema": {
              "type": "string"
            },
            "example": "https://www.acme-plumbing.example"
          },
          {
            "name": "lang",
            "in": "query",
            "description": "The language of the check. Without it, the browser language.",
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "de",
                "fr",
                "es"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page with the check.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/public/collections/{name}/documents": {
      "post": {
        "operationId": "createPublicDocument",
        "tags": [
          "Data"
        ],
        "summary": "Send a form entry (public)",
        "description": "Called from a form on a website by form.js (<script src=\"https://api.hypamoon.com/form.js\" defer></script>); you rarely call it yourself. Needs the project's public token in ?token= (the same token as the chat widget, from POST /chat-tokens), an Origin header in its allowed_origins, a collection with access.public_write, and always a bot token from GET /public/challenge in turnstile_token. Limits: 30 entries per hour per IP address and 500 per day per collection. The response contains the id and created_at, never the data. If the honeypot is filled in, the response is the same but nothing is stored. The collection's triggers fire as they do for the API.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The name of the collection, like contact.",
            "schema": {
              "type": "string"
            },
            "example": "contact"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicDocumentInput"
              },
              "example": {
                "data": {
                  "name": "Anna de Vries",
                  "email": "anna@example.com",
                  "message": "Can you come and look at a leaking tap on Friday?"
                },
                "turnstile_token": "0.zXq3...from-the-challenge-frame"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDocumentCreated"
                },
                "example": {
                  "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                  "created_at": "2026-10-01T09:30:00.000Z",
                  "test": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed, collection_not_public, turnstile_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed",
              "collection_not_public",
              "turnstile_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed",
                            "https://hypamoon.com/docs/errors/collection_not_public",
                            "https://hypamoon.com/docs/errors/turnstile_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, schema_mismatch, document_too_large, document_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "schema_mismatch",
              "document_too_large",
              "document_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/schema_mismatch",
                            "https://hypamoon.com/docs/errors/document_too_large",
                            "https://hypamoon.com/docs/errors/document_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable, service_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable",
              "service_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable",
                            "https://hypamoon.com/docs/errors/service_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listPublicDocuments",
        "tags": [
          "Data"
        ],
        "summary": "Read the newest documents (public)",
        "description": "The newest documents of a collection with access.public_read, from a page on an allowed origin, with the public token in ?token=. Only data and created_at, newest first, 100 at most. Useful for a list of events, a menu or reviews that you add with your API key.",
        "x-hypamoon-auth": "public_chat_token",
        "security": [
          {
            "PublicChatToken": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The name of the collection.",
            "schema": {
              "type": "string"
            },
            "example": "menu"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "How many, default 25, at most 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The newest documents.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDocumentList"
                },
                "example": {
                  "data": [
                    {
                      "data": {
                        "dish": "Soup of the day",
                        "price_eur": 6.5
                      },
                      "created_at": "2026-10-01T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: invalid_chat_token, chat_token_revoked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_chat_token",
              "chat_token_revoked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_chat_token",
                            "https://hypamoon.com/docs/errors/chat_token_revoked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: origin_not_allowed, collection_not_public.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "origin_not_allowed",
              "collection_not_public"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/origin_not_allowed",
                            "https://hypamoon.com/docs/errors/collection_not_public"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "Service Unavailable: chat_unavailable.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "chat_unavailable"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/chat_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": [
          "MCP"
        ],
        "summary": "Model Context Protocol endpoint",
        "description": "The MCP server for agents (Claude Code, Cursor, Lovable and other MCP hosts). Stateless Streamable HTTP: one JSON-RPC 2.0 message per POST, answered with JSON; notifications get 202. Authenticate with the same API key as the REST API, or with an OAuth access token (Claude.ai, ChatGPT and other hosts that connect by URL); the scopes of the key or the connection determine which tools are listed. Without valid credentials the response is 401 with a WWW-Authenticate header that points to the OAuth metadata. See https://hypamoon.com/docs/mcp.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/McpRequest"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/list"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The JSON-RPC response.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpResponse"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "id": 1,
                  "result": {
                    "tools": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted. Carries a WWW-Authenticate header with resource_metadata, so an MCP client can start OAuth.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "WWW-Authenticate": {
                "$ref": "#/components/headers/WWW-Authenticate"
              }
            },
            "x-hypamoon-errors": [
              "unauthorized",
              "missing_api_key",
              "invalid_api_key",
              "api_key_revoked",
              "project_deleted"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/unauthorized",
                            "https://hypamoon.com/docs/errors/missing_api_key",
                            "https://hypamoon.com/docs/errors/invalid_api_key",
                            "https://hypamoon.com/docs/errors/api_key_revoked",
                            "https://hypamoon.com/docs/errors/project_deleted"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agents": {
      "get": {
        "operationId": "listAgents",
        "tags": [
          "Agents"
        ],
        "summary": "List the agents",
        "description": "The agents of the project, newest first, each with pending_approvals: the actions waiting for the owner.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The agents.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "instructions": {
                            "type": "string"
                          },
                          "trigger": {
                            "type": "object",
                            "additionalProperties": {}
                          },
                          "actions": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "object",
                              "properties": {
                                "mode": {
                                  "type": "string",
                                  "enum": [
                                    "auto",
                                    "approve"
                                  ]
                                },
                                "endpoint_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "collections": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                }
                              },
                              "required": [
                                "mode"
                              ],
                              "additionalProperties": false
                            }
                          },
                          "limits": {
                            "type": "object",
                            "properties": {
                              "runs_per_day": {
                                "type": "integer"
                              },
                              "actions_per_run": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "runs_per_day",
                              "actions_per_run"
                            ],
                            "additionalProperties": false
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "paused"
                            ]
                          },
                          "mode": {
                            "type": "string",
                            "enum": [
                              "live",
                              "test"
                            ]
                          },
                          "next_run_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "pending_approvals": {
                            "type": "integer"
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The key (key:<id>) or connected app (oauth:<id>) that created the agent."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "updated_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "instructions",
                          "trigger",
                          "actions",
                          "limits",
                          "status",
                          "mode",
                          "next_run_at",
                          "pending_approvals",
                          "created_by",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false,
                        "description": "An agent that keeps working for the project: a trigger, instructions, actions and limits."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                      "name": "Contact request follow-up",
                      "instructions": "When a new contact request comes in, email me a short summary (who, what they ask, how to reach them). If they left a phone number, propose a call back: start a call to that number in which the assistant says we received their request and asks when we can help.",
                      "trigger": {
                        "type": "document.created",
                        "collection": "contact_requests",
                        "collection_id": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
                        "phone_field": "phone"
                      },
                      "actions": {
                        "email_owner": {
                          "mode": "auto"
                        },
                        "start_call": {
                          "mode": "approve"
                        }
                      },
                      "limits": {
                        "runs_per_day": 20,
                        "actions_per_run": 3
                      },
                      "status": "active",
                      "mode": "test",
                      "next_run_at": null,
                      "pending_approvals": 1,
                      "created_by": "key:c16a7b81-92a3-44b5-8fc6-f708192a3b4d",
                      "created_at": "2026-10-05T09:00:00.000Z",
                      "updated_at": "2026-10-05T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Create an agent",
        "description": "An agent keeps working for your project after the chat: on a trigger (a new document in a collection, a finished call, a closed chat conversation, or a schedule of at least every 15 minutes) it follows your instructions with a small set of actions on Hypamoon: read or update a document, email the owner, send an SMS, start a call, send a webhook to one of your own endpoints. Default deny: an action that is not turned on is refused. SMS and calls go only to the phone number of the event (trigger.phone_field of the document, the other side of the call, or the customer of the chat), at most once per number per day, never to the project's own numbers; they wait for the owner's approval, and always do so when the trigger can come from a stranger (a public form, a call, a chat). In test mode they are simulated; in live mode they need a verified project. Limits: runs per day (default 20) and actions per run (default 3). At most 10 agents per project. Every run is logged with its trigger, a short thought, each action and the model cost. The key needs the scope of every action it turns on (calls:write for start_call, messages:write for send_sms, data:write for update_document, webhooks:write for send_webhook) and of the trigger.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
                    "description": "A short name, like New contact request follow-up."
                  },
                  "instructions": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000,
                    "pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
                    "description": "What the agent should do, in plain language, like: Send the owner a short summary of every new contact request and propose a call back."
                  },
                  "trigger": {
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "document.created"
                          },
                          "collection": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "description": "The id or the name of the collection, like contact_requests."
                          },
                          "phone_field": {
                            "type": "string",
                            "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
                            "description": "The field of the document that holds the phone number, like phone. Needed for send_sms and start_call; the agent never takes a number from anywhere else."
                          },
                          "email_field": {
                            "type": "string",
                            "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
                            "description": "The field of the document that holds the email address, like email. Needed for email_contact; the agent never takes an address from anywhere else."
                          }
                        },
                        "required": [
                          "type",
                          "collection"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "call.completed"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "conversation.closed"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "schedule"
                          },
                          "every_minutes": {
                            "type": "integer",
                            "minimum": 15,
                            "maximum": 10080
                          }
                        },
                        "required": [
                          "type",
                          "every_minutes"
                        ],
                        "additionalProperties": false
                      }
                    ],
                    "discriminator": {
                      "propertyName": "type"
                    },
                    "description": "When the agent runs: a new document in a collection, a finished call, a closed chat conversation (once its summary is ready), or a schedule (every_minutes, at least 15)."
                  },
                  "actions": {
                    "type": "object",
                    "properties": {
                      "read_document": {
                        "type": "object",
                        "properties": {
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 64
                            },
                            "minItems": 1,
                            "maxItems": 5,
                            "description": "The collections (names or ids) this action may use. Default: only the collection of a document.created trigger."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Read a document of the trigger's collection, or of the collections you list. Always without asking."
                      },
                      "update_document": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 64
                            },
                            "minItems": 1,
                            "maxItems": 5,
                            "description": "The collections (names or ids) this action may use. Default: only the collection of a document.created trigger."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Change fields of a document (a merge) in the trigger's collection, or in the collections you list. Default mode auto."
                      },
                      "email_owner": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Email the project owner (the billing email of the project). Nobody else. Default mode auto."
                      },
                      "email_contact": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "from": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 320,
                            "description": "The sender, on a verified sending domain of this project, like \"Acme Plumbing <hello@acmeplumbing.com>\"."
                          },
                          "reply_to": {
                            "type": "string",
                            "maxLength": 254,
                            "description": "Optional. Where replies go, like the owner's mailbox."
                          }
                        },
                        "required": [
                          "from"
                        ],
                        "additionalProperties": false,
                        "description": "Only with the Mail module. Email the person of the event: the address in trigger.email_field of the document, or the customer of the chat. No other address. Default mode approve; always approve for a public form or a chat. At most one per address per day. Simulated in test mode."
                      },
                      "send_sms": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Send an SMS to the phone number of the event (trigger.phone_field of the document, the other side of the call, or the customer of the chat). Default mode approve; always approve for a public form, a call or a chat. At most one per number per day. Simulated in test mode."
                      },
                      "start_call": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Let the project's assistant call the phone number of the event (as for send_sms). Default mode approve; always approve for a public form, a call or a chat. At most one per number per day. Simulated in test mode."
                      },
                      "send_webhook": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "endpoint_id": {
                            "type": "string",
                            "format": "uuid"
                          }
                        },
                        "required": [
                          "endpoint_id"
                        ],
                        "additionalProperties": false,
                        "description": "POST a signed agent.action event to one of the project's own webhook endpoints (list_webhooks). Default mode auto."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The actions this agent may take. Anything not listed is denied."
                  },
                  "limits": {
                    "type": "object",
                    "properties": {
                      "runs_per_day": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 200,
                        "description": "Runs in any 24 hours; above it a run is skipped and logged. Default 20."
                      },
                      "actions_per_run": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10,
                        "description": "Actions the agent may take in one run, refused ones included. Default 3."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The limits of the agent."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ],
                    "description": "active (default) or paused."
                  }
                },
                "required": [
                  "name",
                  "instructions",
                  "trigger",
                  "actions"
                ],
                "additionalProperties": false,
                "description": "A new agent."
              },
              "example": {
                "name": "Contact request follow-up",
                "instructions": "When a new contact request comes in, email me a short summary (who, what they ask, how to reach them). If they left a phone number, propose a call back: start a call to that number in which the assistant says we received their request and asks when we can help.",
                "trigger": {
                  "type": "document.created",
                  "collection": "contact_requests",
                  "phone_field": "phone"
                },
                "actions": {
                  "email_owner": {
                    "mode": "auto"
                  },
                  "start_call": {
                    "mode": "approve"
                  }
                },
                "limits": {
                  "runs_per_day": 20,
                  "actions_per_run": 3
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new agent.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "instructions": {
                      "type": "string"
                    },
                    "trigger": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "actions": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ]
                          },
                          "endpoint_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "mode"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "runs_per_day": {
                          "type": "integer"
                        },
                        "actions_per_run": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "runs_per_day",
                        "actions_per_run"
                      ],
                      "additionalProperties": false
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "paused"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "next_run_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "pending_approvals": {
                      "type": "integer"
                    },
                    "created_by": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The key (key:<id>) or connected app (oauth:<id>) that created the agent."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "instructions",
                    "trigger",
                    "actions",
                    "limits",
                    "status",
                    "mode",
                    "next_run_at",
                    "pending_approvals",
                    "created_by",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An agent that keeps working for the project: a trigger, instructions, actions and limits."
                },
                "example": {
                  "id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                  "name": "Contact request follow-up",
                  "instructions": "When a new contact request comes in, email me a short summary (who, what they ask, how to reach them). If they left a phone number, propose a call back: start a call to that number in which the assistant says we received their request and asks when we can help.",
                  "trigger": {
                    "type": "document.created",
                    "collection": "contact_requests",
                    "collection_id": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
                    "phone_field": "phone"
                  },
                  "actions": {
                    "email_owner": {
                      "mode": "auto"
                    },
                    "start_call": {
                      "mode": "approve"
                    }
                  },
                  "limits": {
                    "runs_per_day": 20,
                    "actions_per_run": 3
                  },
                  "status": "active",
                  "mode": "test",
                  "next_run_at": null,
                  "pending_approvals": 0,
                  "created_by": "key:c16a7b81-92a3-44b5-8fc6-f708192a3b4d",
                  "created_at": "2026-10-05T09:00:00.000Z",
                  "updated_at": "2026-10-05T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agents/{id}": {
      "get": {
        "operationId": "getAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Get an agent",
        "description": "One agent with its five newest runs, each with its actions (including those waiting for approval).",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agent.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "instructions": {
                      "type": "string"
                    },
                    "trigger": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "actions": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ]
                          },
                          "endpoint_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "mode"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "runs_per_day": {
                          "type": "integer"
                        },
                        "actions_per_run": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "runs_per_day",
                        "actions_per_run"
                      ],
                      "additionalProperties": false
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "paused"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "next_run_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "pending_approvals": {
                      "type": "integer"
                    },
                    "created_by": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The key (key:<id>) or connected app (oauth:<id>) that created the agent."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    },
                    "recent_runs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "agent_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "running",
                              "done",
                              "failed",
                              "skipped"
                            ]
                          },
                          "trigger": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "enum": [
                                  "document.created",
                                  "call.completed",
                                  "conversation.closed",
                                  "schedule"
                                ]
                              },
                              "id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "scheduled_for": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "type",
                              "id",
                              "scheduled_for"
                            ],
                            "additionalProperties": false
                          },
                          "thought": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "What the agent thought, in one or two sentences."
                          },
                          "outcome": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "What happened, for each action."
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "model_turns": {
                            "type": "integer"
                          },
                          "input_tokens": {
                            "type": "integer",
                            "description": "Tokens the language model read in this run, including retries."
                          },
                          "output_tokens": {
                            "type": "integer",
                            "description": "Tokens the language model wrote in this run, including retries."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "started_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "finished_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "actions": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "run_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "agent_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "read_document",
                                    "update_document",
                                    "email_owner",
                                    "email_contact",
                                    "send_sms",
                                    "start_call",
                                    "send_webhook"
                                  ]
                                },
                                "input": {
                                  "type": "object",
                                  "additionalProperties": {},
                                  "description": "What the agent asked for, like {\"to\": \"+447700900123\", \"assistant_instructions\": \"...\"}."
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "running",
                                    "done",
                                    "simulated",
                                    "pending_approval",
                                    "rejected",
                                    "refused",
                                    "failed",
                                    "expired"
                                  ],
                                  "description": "running, done, simulated (test mode), pending_approval (waiting for the owner), rejected (by the owner), refused (by the limits or the rules, see reason), failed (see reason), expired (no decision within 72 hours)."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "result": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "additionalProperties": {}
                                },
                                "decided_by": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "decided_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "expires_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When a pending action expires."
                                },
                                "created_at": {
                                  "type": "string"
                                },
                                "executed_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "id",
                                "run_id",
                                "agent_id",
                                "type",
                                "input",
                                "status",
                                "reason",
                                "result",
                                "decided_by",
                                "decided_at",
                                "expires_at",
                                "created_at",
                                "executed_at"
                              ],
                              "additionalProperties": false,
                              "description": "One action of an agent run."
                            }
                          }
                        },
                        "required": [
                          "id",
                          "agent_id",
                          "status",
                          "trigger",
                          "thought",
                          "outcome",
                          "error",
                          "model_turns",
                          "input_tokens",
                          "output_tokens",
                          "created_at",
                          "started_at",
                          "finished_at",
                          "actions"
                        ],
                        "additionalProperties": false,
                        "description": "One run of an agent: the log of what it did and why."
                      }
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "instructions",
                    "trigger",
                    "actions",
                    "limits",
                    "status",
                    "mode",
                    "next_run_at",
                    "pending_approvals",
                    "created_by",
                    "created_at",
                    "updated_at",
                    "recent_runs"
                  ],
                  "additionalProperties": false,
                  "description": "An agent with its five newest runs."
                },
                "example": {
                  "id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                  "name": "Contact request follow-up",
                  "instructions": "When a new contact request comes in, email me a short summary (who, what they ask, how to reach them). If they left a phone number, propose a call back: start a call to that number in which the assistant says we received their request and asks when we can help.",
                  "trigger": {
                    "type": "document.created",
                    "collection": "contact_requests",
                    "collection_id": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
                    "phone_field": "phone"
                  },
                  "actions": {
                    "email_owner": {
                      "mode": "auto"
                    },
                    "start_call": {
                      "mode": "approve"
                    }
                  },
                  "limits": {
                    "runs_per_day": 20,
                    "actions_per_run": 3
                  },
                  "status": "active",
                  "mode": "test",
                  "next_run_at": null,
                  "pending_approvals": 1,
                  "created_by": "key:c16a7b81-92a3-44b5-8fc6-f708192a3b4d",
                  "created_at": "2026-10-05T09:00:00.000Z",
                  "updated_at": "2026-10-05T09:00:00.000Z",
                  "recent_runs": [
                    {
                      "id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                      "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                      "status": "done",
                      "trigger": {
                        "type": "document.created",
                        "id": "9c40e5f6-7d81-4293-ad04-c5e6f708192a",
                        "scheduled_for": null
                      },
                      "thought": "A new contact request from Sam about a kitchen quote: I email the owner a summary and propose a call back.",
                      "outcome": "email_owner simulated; start_call waiting for approval. The owner has the summary; the call waits for approval.",
                      "error": null,
                      "model_turns": 2,
                      "input_tokens": 4210,
                      "output_tokens": 312,
                      "created_at": "2026-10-05T09:01:00.000Z",
                      "started_at": "2026-10-05T09:01:02.000Z",
                      "finished_at": "2026-10-05T09:01:06.000Z",
                      "actions": [
                        {
                          "id": "a94f5e6f-7081-4293-8da4-d5e6f708192b",
                          "run_id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                          "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                          "type": "email_owner",
                          "input": {
                            "subject": "New contact request: Sam, kitchen quote",
                            "body": "Sam asks for a quote for a new kitchen. Phone +447700900123."
                          },
                          "status": "simulated",
                          "reason": null,
                          "result": {
                            "to": "owner",
                            "subject": "New contact request: Sam, kitchen quote"
                          },
                          "decided_by": null,
                          "decided_at": null,
                          "expires_at": null,
                          "created_at": "2026-10-05T09:01:04.000Z",
                          "executed_at": "2026-10-05T09:01:04.000Z"
                        },
                        {
                          "id": "8b3f4d5e-6c70-4182-9c93-b4d5e6f70819",
                          "run_id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                          "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                          "type": "start_call",
                          "input": {
                            "to": "+447700900123",
                            "assistant_instructions": "Call Sam back: we received the contact request about a quote for a new kitchen and ask when we can help."
                          },
                          "status": "pending_approval",
                          "reason": null,
                          "result": null,
                          "decided_by": null,
                          "decided_at": null,
                          "expires_at": "2026-10-08T09:01:05.000Z",
                          "created_at": "2026-10-05T09:01:05.000Z",
                          "executed_at": null
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Change an agent",
        "description": "Changes the name, instructions, trigger, actions (the whole set: anything not listed is denied), limits or status. Fields you leave out stay as they are. status paused stops new runs; active starts them again.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agent.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
                    "description": "A short name, like New contact request follow-up."
                  },
                  "instructions": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000,
                    "pattern": "^[^\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
                    "description": "What the agent should do, in plain language, like: Send the owner a short summary of every new contact request and propose a call back."
                  },
                  "trigger": {
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "document.created"
                          },
                          "collection": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "description": "The id or the name of the collection, like contact_requests."
                          },
                          "phone_field": {
                            "type": "string",
                            "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
                            "description": "The field of the document that holds the phone number, like phone. Needed for send_sms and start_call; the agent never takes a number from anywhere else."
                          },
                          "email_field": {
                            "type": "string",
                            "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
                            "description": "The field of the document that holds the email address, like email. Needed for email_contact; the agent never takes an address from anywhere else."
                          }
                        },
                        "required": [
                          "type",
                          "collection"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "call.completed"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "conversation.closed"
                          }
                        },
                        "required": [
                          "type"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "const": "schedule"
                          },
                          "every_minutes": {
                            "type": "integer",
                            "minimum": 15,
                            "maximum": 10080
                          }
                        },
                        "required": [
                          "type",
                          "every_minutes"
                        ],
                        "additionalProperties": false
                      }
                    ],
                    "discriminator": {
                      "propertyName": "type"
                    },
                    "description": "When the agent runs: a new document in a collection, a finished call, a closed chat conversation (once its summary is ready), or a schedule (every_minutes, at least 15)."
                  },
                  "actions": {
                    "type": "object",
                    "properties": {
                      "read_document": {
                        "type": "object",
                        "properties": {
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 64
                            },
                            "minItems": 1,
                            "maxItems": 5,
                            "description": "The collections (names or ids) this action may use. Default: only the collection of a document.created trigger."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Read a document of the trigger's collection, or of the collections you list. Always without asking."
                      },
                      "update_document": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 64
                            },
                            "minItems": 1,
                            "maxItems": 5,
                            "description": "The collections (names or ids) this action may use. Default: only the collection of a document.created trigger."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Change fields of a document (a merge) in the trigger's collection, or in the collections you list. Default mode auto."
                      },
                      "email_owner": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Email the project owner (the billing email of the project). Nobody else. Default mode auto."
                      },
                      "email_contact": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "from": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 320,
                            "description": "The sender, on a verified sending domain of this project, like \"Acme Plumbing <hello@acmeplumbing.com>\"."
                          },
                          "reply_to": {
                            "type": "string",
                            "maxLength": 254,
                            "description": "Optional. Where replies go, like the owner's mailbox."
                          }
                        },
                        "required": [
                          "from"
                        ],
                        "additionalProperties": false,
                        "description": "Only with the Mail module. Email the person of the event: the address in trigger.email_field of the document, or the customer of the chat. No other address. Default mode approve; always approve for a public form or a chat. At most one per address per day. Simulated in test mode."
                      },
                      "send_sms": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Send an SMS to the phone number of the event (trigger.phone_field of the document, the other side of the call, or the customer of the chat). Default mode approve; always approve for a public form, a call or a chat. At most one per number per day. Simulated in test mode."
                      },
                      "start_call": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          }
                        },
                        "additionalProperties": false,
                        "description": "Let the project's assistant call the phone number of the event (as for send_sms). Default mode approve; always approve for a public form, a call or a chat. At most one per number per day. Simulated in test mode."
                      },
                      "send_webhook": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ],
                            "description": "auto: the agent does it without asking. approve: the action waits until the owner approves it (in the portal or with approve_agent_action)."
                          },
                          "endpoint_id": {
                            "type": "string",
                            "format": "uuid"
                          }
                        },
                        "required": [
                          "endpoint_id"
                        ],
                        "additionalProperties": false,
                        "description": "POST a signed agent.action event to one of the project's own webhook endpoints (list_webhooks). Default mode auto."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The actions this agent may take. Anything not listed is denied."
                  },
                  "limits": {
                    "type": "object",
                    "properties": {
                      "runs_per_day": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 200,
                        "description": "Runs in any 24 hours; above it a run is skipped and logged. Default 20."
                      },
                      "actions_per_run": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10,
                        "description": "Actions the agent may take in one run, refused ones included. Default 3."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The limits of the agent."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ],
                    "description": "paused stops new runs; active starts them again."
                  }
                },
                "additionalProperties": false,
                "description": "Changes to an agent. Fields you leave out stay as they are."
              },
              "example": {
                "actions": {
                  "email_owner": {
                    "mode": "auto"
                  },
                  "start_call": {
                    "mode": "approve"
                  },
                  "send_sms": {
                    "mode": "approve"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent as it is now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "instructions": {
                      "type": "string"
                    },
                    "trigger": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "actions": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "auto",
                              "approve"
                            ]
                          },
                          "endpoint_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "collections": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "mode"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "runs_per_day": {
                          "type": "integer"
                        },
                        "actions_per_run": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "runs_per_day",
                        "actions_per_run"
                      ],
                      "additionalProperties": false
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "paused"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "next_run_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "pending_approvals": {
                      "type": "integer"
                    },
                    "created_by": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The key (key:<id>) or connected app (oauth:<id>) that created the agent."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "instructions",
                    "trigger",
                    "actions",
                    "limits",
                    "status",
                    "mode",
                    "next_run_at",
                    "pending_approvals",
                    "created_by",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An agent that keeps working for the project: a trigger, instructions, actions and limits."
                },
                "example": {
                  "id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                  "name": "Contact request follow-up",
                  "instructions": "When a new contact request comes in, email me a short summary (who, what they ask, how to reach them). If they left a phone number, propose a call back: start a call to that number in which the assistant says we received their request and asks when we can help.",
                  "trigger": {
                    "type": "document.created",
                    "collection": "contact_requests",
                    "collection_id": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
                    "phone_field": "phone"
                  },
                  "actions": {
                    "email_owner": {
                      "mode": "auto"
                    },
                    "start_call": {
                      "mode": "approve"
                    }
                  },
                  "limits": {
                    "runs_per_day": 20,
                    "actions_per_run": 3
                  },
                  "status": "active",
                  "mode": "test",
                  "next_run_at": null,
                  "pending_approvals": 1,
                  "created_by": "key:c16a7b81-92a3-44b5-8fc6-f708192a3b4d",
                  "created_at": "2026-10-05T09:00:00.000Z",
                  "updated_at": "2026-10-05T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Delete an agent",
        "description": "Stops the agent permanently: no new runs, actions waiting for approval expire, and the agent's own key is revoked. Its log stays readable until the project is deleted. Deleting it again returns 204 as well.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agent.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agents/{id}/runs": {
      "get": {
        "operationId": "listAgentRuns",
        "tags": [
          "Agents"
        ],
        "summary": "List the runs of an agent",
        "description": "The log of an agent, newest first: for each run the trigger, what the agent thought, each action with its status (including refused and pending ones), the outcome and the tokens used. Thoughts, outcomes and action input are kept for 90 days. Paginate with before (the created_at of the last run you have).",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agent.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Runs per page, 1 to 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "Only runs created before this moment.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The runs.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "agent_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "running",
                              "done",
                              "failed",
                              "skipped"
                            ]
                          },
                          "trigger": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "enum": [
                                  "document.created",
                                  "call.completed",
                                  "conversation.closed",
                                  "schedule"
                                ]
                              },
                              "id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "scheduled_for": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "type",
                              "id",
                              "scheduled_for"
                            ],
                            "additionalProperties": false
                          },
                          "thought": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "What the agent thought, in one or two sentences."
                          },
                          "outcome": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "What happened, for each action."
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "model_turns": {
                            "type": "integer"
                          },
                          "input_tokens": {
                            "type": "integer",
                            "description": "Tokens the language model read in this run, including retries."
                          },
                          "output_tokens": {
                            "type": "integer",
                            "description": "Tokens the language model wrote in this run, including retries."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "started_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "finished_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "actions": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "run_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "agent_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "read_document",
                                    "update_document",
                                    "email_owner",
                                    "email_contact",
                                    "send_sms",
                                    "start_call",
                                    "send_webhook"
                                  ]
                                },
                                "input": {
                                  "type": "object",
                                  "additionalProperties": {},
                                  "description": "What the agent asked for, like {\"to\": \"+447700900123\", \"assistant_instructions\": \"...\"}."
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "running",
                                    "done",
                                    "simulated",
                                    "pending_approval",
                                    "rejected",
                                    "refused",
                                    "failed",
                                    "expired"
                                  ],
                                  "description": "running, done, simulated (test mode), pending_approval (waiting for the owner), rejected (by the owner), refused (by the limits or the rules, see reason), failed (see reason), expired (no decision within 72 hours)."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "result": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "additionalProperties": {}
                                },
                                "decided_by": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "decided_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "expires_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When a pending action expires."
                                },
                                "created_at": {
                                  "type": "string"
                                },
                                "executed_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "id",
                                "run_id",
                                "agent_id",
                                "type",
                                "input",
                                "status",
                                "reason",
                                "result",
                                "decided_by",
                                "decided_at",
                                "expires_at",
                                "created_at",
                                "executed_at"
                              ],
                              "additionalProperties": false,
                              "description": "One action of an agent run."
                            }
                          }
                        },
                        "required": [
                          "id",
                          "agent_id",
                          "status",
                          "trigger",
                          "thought",
                          "outcome",
                          "error",
                          "model_turns",
                          "input_tokens",
                          "output_tokens",
                          "created_at",
                          "started_at",
                          "finished_at",
                          "actions"
                        ],
                        "additionalProperties": false,
                        "description": "One run of an agent: the log of what it did and why."
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data",
                    "has_more"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                      "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                      "status": "done",
                      "trigger": {
                        "type": "document.created",
                        "id": "9c40e5f6-7d81-4293-ad04-c5e6f708192a",
                        "scheduled_for": null
                      },
                      "thought": "A new contact request from Sam about a kitchen quote: I email the owner a summary and propose a call back.",
                      "outcome": "email_owner simulated; start_call waiting for approval. The owner has the summary; the call waits for approval.",
                      "error": null,
                      "model_turns": 2,
                      "input_tokens": 4210,
                      "output_tokens": 312,
                      "created_at": "2026-10-05T09:01:00.000Z",
                      "started_at": "2026-10-05T09:01:02.000Z",
                      "finished_at": "2026-10-05T09:01:06.000Z",
                      "actions": [
                        {
                          "id": "a94f5e6f-7081-4293-8da4-d5e6f708192b",
                          "run_id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                          "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                          "type": "email_owner",
                          "input": {
                            "subject": "New contact request: Sam, kitchen quote",
                            "body": "Sam asks for a quote for a new kitchen. Phone +447700900123."
                          },
                          "status": "simulated",
                          "reason": null,
                          "result": {
                            "to": "owner",
                            "subject": "New contact request: Sam, kitchen quote"
                          },
                          "decided_by": null,
                          "decided_at": null,
                          "expires_at": null,
                          "created_at": "2026-10-05T09:01:04.000Z",
                          "executed_at": "2026-10-05T09:01:04.000Z"
                        },
                        {
                          "id": "8b3f4d5e-6c70-4182-9c93-b4d5e6f70819",
                          "run_id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                          "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                          "type": "start_call",
                          "input": {
                            "to": "+447700900123",
                            "assistant_instructions": "Call Sam back: we received the contact request about a quote for a new kitchen and ask when we can help."
                          },
                          "status": "pending_approval",
                          "reason": null,
                          "result": null,
                          "decided_by": null,
                          "decided_at": null,
                          "expires_at": "2026-10-08T09:01:05.000Z",
                          "created_at": "2026-10-05T09:01:05.000Z",
                          "executed_at": null
                        }
                      ]
                    }
                  ],
                  "has_more": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agent-actions/{id}/decide": {
      "post": {
        "operationId": "decideAgentAction",
        "tags": [
          "Agents"
        ],
        "summary": "Approve or reject an agent action",
        "description": "The owner's decision on an action awaiting approval (status pending_approval). approve runs it now, through the same checks as any other request (verification, balance, test mode); reject drops it. An SMS or a call can only be approved in the portal (403 forbidden here); rejecting does work here. Approving also needs the action's scope. A paused agent's actions can only be rejected. Repeating the same decision returns the action unchanged; a different decision on an action that was already decided or expired returns 409. Every decision is recorded in the project's audit log.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agent action.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "reject"
                    ],
                    "description": "approve runs the action now; reject drops it."
                  }
                },
                "required": [
                  "decision"
                ],
                "additionalProperties": false,
                "description": "The owner's decision on an action that waits for approval."
              },
              "example": {
                "decision": "approve"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The action after the decision.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "run_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agent_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "read_document",
                        "update_document",
                        "email_owner",
                        "email_contact",
                        "send_sms",
                        "start_call",
                        "send_webhook"
                      ]
                    },
                    "input": {
                      "type": "object",
                      "additionalProperties": {},
                      "description": "What the agent asked for, like {\"to\": \"+447700900123\", \"assistant_instructions\": \"...\"}."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "running",
                        "done",
                        "simulated",
                        "pending_approval",
                        "rejected",
                        "refused",
                        "failed",
                        "expired"
                      ],
                      "description": "running, done, simulated (test mode), pending_approval (waiting for the owner), rejected (by the owner), refused (by the limits or the rules, see reason), failed (see reason), expired (no decision within 72 hours)."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "result": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": {}
                    },
                    "decided_by": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "decided_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "When a pending action expires."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "executed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "run_id",
                    "agent_id",
                    "type",
                    "input",
                    "status",
                    "reason",
                    "result",
                    "decided_by",
                    "decided_at",
                    "expires_at",
                    "created_at",
                    "executed_at"
                  ],
                  "additionalProperties": false,
                  "description": "One action of an agent run."
                },
                "example": {
                  "id": "8b3f4d5e-6c70-4182-9c93-b4d5e6f70819",
                  "run_id": "7a2e3c4d-5b6f-4071-8b82-a3c4d5e6f708",
                  "agent_id": "6f1d2b3c-4a5e-4f60-8a71-92b3c4d5e6f7",
                  "type": "start_call",
                  "input": {
                    "to": "+447700900123",
                    "assistant_instructions": "Call Sam back: we received the contact request about a quote for a new kitchen and ask when we can help."
                  },
                  "status": "simulated",
                  "reason": null,
                  "result": {
                    "call_id": "b05f6a70-8192-43a4-9eb5-e6f708192a3c",
                    "status": "completed",
                    "to": "+447700900123"
                  },
                  "decided_by": "user:d27b8c92-a3b4-45c6-90d7-08192a3b4c5e",
                  "decided_at": "2026-10-05T09:10:00.000Z",
                  "expires_at": null,
                  "created_at": "2026-10-05T09:01:05.000Z",
                  "executed_at": "2026-10-05T09:10:01.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps": {
      "get": {
        "operationId": "listApps",
        "tags": [
          "Apps"
        ],
        "summary": "List the apps",
        "description": "The project's apps, newest first.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The apps.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "site_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "url": {
                            "type": "string",
                            "description": "The site; the app answers on url + /api."
                          },
                          "api_url": {
                            "type": "string",
                            "description": "Where the app's /api starts."
                          },
                          "mode": {
                            "type": "string",
                            "enum": [
                              "live",
                              "test"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "offline"
                            ],
                            "description": "offline: Hypamoon took the site offline; the app does not run."
                          },
                          "version": {
                            "type": "integer",
                            "description": "The version that runs now."
                          },
                          "allowed_hosts": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "secrets": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "schedules": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "cron": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "cron"
                              ],
                              "additionalProperties": false
                            }
                          },
                          "limits": {
                            "type": "object",
                            "properties": {
                              "cpu_ms": {
                                "type": "integer"
                              },
                              "subrequests": {
                                "type": "integer"
                              },
                              "outbound_per_minute": {
                                "type": "integer"
                              },
                              "outbound_per_day": {
                                "type": "integer"
                              },
                              "bundle_bytes": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "cpu_ms",
                              "subrequests",
                              "outbound_per_minute",
                              "outbound_per_day",
                              "bundle_bytes"
                            ],
                            "additionalProperties": false,
                            "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                          },
                          "auth": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "signup": {
                                "type": "string",
                                "enum": [
                                  "invite",
                                  "open",
                                  "approval",
                                  "domain"
                                ]
                              },
                              "roles": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "default_role": {
                                "type": "string"
                              },
                              "google": {
                                "type": "boolean"
                              },
                              "protect": {
                                "type": "string",
                                "enum": [
                                  "site",
                                  "none"
                                ]
                              },
                              "session_days": {
                                "type": "integer"
                              },
                              "admin_session_days": {
                                "type": "integer"
                              },
                              "allowed_domains": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "buyers": {
                                "type": "boolean"
                              },
                              "groups": {
                                "type": "boolean"
                              },
                              "login_url": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "signup",
                              "roles",
                              "default_role",
                              "google",
                              "login_url"
                            ],
                            "additionalProperties": false,
                            "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                          },
                          "database": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "bytes": {
                                "type": "integer"
                              },
                              "full": {
                                "type": "boolean"
                              },
                              "per_user_tables": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "table": {
                                      "type": "string"
                                    },
                                    "column": {
                                      "type": "string"
                                    },
                                    "admin_role": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "table",
                                    "column",
                                    "admin_role"
                                  ],
                                  "additionalProperties": false
                                },
                                "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                              }
                            },
                            "required": [
                              "bytes",
                              "full"
                            ],
                            "additionalProperties": false,
                            "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                          },
                          "kept": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                          },
                          "warnings": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "updated_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "site_id",
                          "url",
                          "api_url",
                          "mode",
                          "status",
                          "version",
                          "allowed_hosts",
                          "secrets",
                          "schedules",
                          "limits",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false,
                        "description": "An app: a site with server code on /api."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                      "name": "Contact form",
                      "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                      "url": "https://contact-demo.hypamoon.app",
                      "api_url": "https://contact-demo.hypamoon.app/api",
                      "mode": "test",
                      "status": "active",
                      "version": 2,
                      "allowed_hosts": [
                        "api.weatherapi.com"
                      ],
                      "secrets": [
                        "weather_key"
                      ],
                      "schedules": [
                        {
                          "cron": "0 7 * * *"
                        }
                      ],
                      "limits": {
                        "cpu_ms": 10,
                        "subrequests": 10,
                        "outbound_per_minute": 60,
                        "outbound_per_day": 2000,
                        "bundle_bytes": 1048576
                      },
                      "created_at": "2026-10-06T09:00:00.000Z",
                      "updated_at": "2026-10-06T09:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createApp",
        "tags": [
          "Apps"
        ],
        "summary": "Deploy an app",
        "description": "An app is a site with server code: requests to /api and everything below it on the site go to your code, all other paths keep serving the site's static files. Same address, no CORS; the site's password, paid access and own domain apply to /api too. The code is one bundled ES module (plus optional extra modules) in the standard module worker format (export default { fetch, scheduled }); no build step runs here. fetch() only reaches allowed_hosts and the Hypamoon API (the project's own data, with a key Hypamoon adds). To use a secret from the vault, send the header x-hypamoon-secret: <name>; the code never sees its value. Each app runs isolated, with limits per request (CPU time, fetch calls) and an outbound budget per minute and per day. The first deploy for a site creates the app (201); a deploy for a site that already has one publishes a new version (200). On a new version, every setting you leave out (allowed_hosts, secrets, schedules, vars, auth, database, fetch_pages) keeps its current value, and the answer lists them in kept. Clear one explicitly: [] or {} for a list or vars, null for auth, false for fetch_pages. database: false on an app that has a database is refused (409): a deploy never disconnects a database. The code works the same way: leave main_module and modules out to keep the running code (to change only settings, or to move a test app to live), or send only the changed modules with keep_existing_modules: true. A new app always needs main_module and modules. The ten newest versions are kept for rollback. Before anything goes live the code is checked for mining, hidden or downloaded code and fake login pages; there is no option to skip that check. A live app needs a verified project. An app that runs in test mode moves to live when you deploy it with a live key once the project is verified: it keeps its database and its users. A live app cannot go back to test (key_mode_mismatch).",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The site the app belongs to (from deploy_site or list_sites). Requests to /api and everything below it on that site go to the app; all other paths stay the site's static files."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "A name for your own overview. Defaults to the site's name."
                  },
                  "main_module": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "The module with export default { async fetch(request, env, ctx) { ... } } (and optionally scheduled). Usually index.js."
                  },
                  "modules": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 100,
                          "description": "The module's file name, like index.js, lib/util.js, data.json or template.html."
                        },
                        "content": {
                          "type": "string",
                          "description": "The module as plain text: bundled JavaScript (an ES module), JSON or text. No build step runs on Hypamoon."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "esm",
                            "json",
                            "text"
                          ],
                          "description": "Leave it out: it follows from the extension (.js and .mjs are esm, .json is json, .txt, .html, .md, .css, .svg and .csv are text)."
                        }
                      },
                      "required": [
                        "name",
                        "content"
                      ],
                      "additionalProperties": false,
                      "description": "One module of the app's server code."
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "The bundled server code: the main module plus at most 19 more (ES modules, JSON or text). No WebAssembly, no npm install: bundle dependencies into the module."
                  },
                  "allowed_hosts": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 253
                    },
                    "maxItems": 20,
                    "description": "Hosts the app may call with fetch(): exact names like api.example.com, or *.example.com for its subdomains. Everything else is blocked. At most 20. The Hypamoon API (for the project's data) is always allowed."
                  },
                  "secrets": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_]{1,39}$"
                    },
                    "maxItems": 10,
                    "description": "Names of secrets from the project's vault (request_secret) the app may use. The code never sees a value: it sends the header x-hypamoon-secret: <name> and Hypamoon puts the secret in the request, only to the hosts of that secret. At most 10."
                  },
                  "schedules": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "cron": {
                          "type": "string",
                          "minLength": 9,
                          "maxLength": 100,
                          "description": "Five fields in UTC, like \"*/15 * * * *\" or \"0 7 * * 1-5\". Minutes in steps of 5 (0, 5, ... 55): schedules run on a five-minute clock."
                        }
                      },
                      "required": [
                        "cron"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 3,
                    "description": "When the app's scheduled(event, env, ctx) handler runs. At most 3."
                  },
                  "vars": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 1024
                    },
                    "propertyNames": {
                      "pattern": "^[A-Z][A-Z0-9_]{0,63}$"
                    },
                    "description": "Plain settings in env, like {\"CITY\": \"Delft\"}. Never a key or password: use secrets. At most 20, names in UPPER_CASE."
                  },
                  "auth": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "signup": {
                        "type": "string",
                        "enum": [
                          "invite",
                          "open",
                          "approval",
                          "domain"
                        ],
                        "description": "invite (the default): only people you add with invite_app_user, or who use an invite link, can sign in. open: anyone with an email address can sign up and gets default_role. approval: anyone can ask to join (also as a waitlist); after confirming their email the request waits until an admin approves it on /_hypamoon/admin. domain: only addresses on allowed_domains can sign up."
                      },
                      "roles": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "pattern": "^[a-z][a-z0-9_-]{0,31}$"
                        },
                        "minItems": 1,
                        "maxItems": 10,
                        "description": "The roles in this app, most powerful first, like [\"admin\", \"member\"] (the default)."
                      },
                      "default_role": {
                        "type": "string",
                        "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                        "description": "The role of someone who signs up on an open app, and of an invite without a role. Defaults to the last role in roles."
                      },
                      "google": {
                        "type": "boolean",
                        "description": "Also offer Continue with Google next to the email link."
                      },
                      "protect": {
                        "type": "string",
                        "enum": [
                          "site",
                          "none"
                        ],
                        "description": "site: every page and file of the site needs a signed-in user; visitors who are not signed in get the sign-in page (only the icon and the web app manifest stay open). none: the pages are public and the code decides per request. /api always stays with the code (use env.HYPAMOON.requireUser). Leave it out to keep the current setting; a new app with signup invite starts with site."
                      },
                      "session_days": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 90,
                        "description": "How many days a sign-in lasts after the last visit (1 to 90, default 90; 7 for a sensitive app). Shorter means people sign in again sooner. The admin role has its own, shorter default: see admin_session_days. Leave it out to keep the current setting."
                      },
                      "admin_session_days": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 90,
                        "description": "How many days a sign-in of the admin role (the first role in roles) lasts after the last visit (1 to 90, default 14), never longer than session_days. Whoever can see and change everything signs in again sooner. A change applies from the next sign-in; sessions that are already open run out as they were. Leave it out to keep the current setting."
                      },
                      "allowed_domains": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 253
                        },
                        "minItems": 1,
                        "maxItems": 20,
                        "description": "Only with signup domain: the email domains that may sign up, like [\"example.com\"]. Exact domains, no subdomains."
                      },
                      "buyers": {
                        "type": "boolean",
                        "description": "true: someone who buys paid access to this site (create_access_product) becomes a user of the app with default_role and an entitlement for what they bought: env.HYPAMOON.user.has(entitlement). They sign in once, with the app's sign-in. Leave it out to keep the current setting."
                      },
                      "groups": {
                        "type": "boolean",
                        "description": "true: the app can put its users in groups (teams), with create_app_group and add_app_group_member or on /_hypamoon/admin. A group can give its members an extra role and an extra entitlement (never the admin role); the code sees env.HYPAMOON.user.groups, inGroup(key), and the extra roles and entitlements in is() and has(). Leave it out to keep the current setting."
                      }
                    },
                    "additionalProperties": false,
                    "description": "Sign-in for the people who use the app. They sign in on the site itself (/_hypamoon/auth/login) with a link by email; Hypamoon keeps only their email address and name. Hypamoon checks the sign-in before a request reaches the code and hands the person over ready to use: env.HYPAMOON.user is { id, email, name, roles, entitlements, groups, is(role), has(entitlement) } or null, env.HYPAMOON.requireUser(request) gives the user or a ready 401 JSON Response, and env.HYPAMOON.requireRole(request, role) the user or a ready 401 or 403 (if (user instanceof Response) return user). Never verify the signature of the x-hypamoon-user headers yourself and never use HYPAMOON_AUTH_SECRET in the code; only forward those two headers unchanged to env.HYPAMOON_API (/app-files, /app-users). The first role in roles is the admin role everywhere."
                  },
                  "database": {
                    "type": "boolean",
                    "description": "true gives the app its own SQL database (SQLite) as env.DB: env.DB.prepare(sql).bind(...).all(). Create and change tables with apply_app_migration. The database stays when you deploy a version without it; delete_app removes it."
                  },
                  "fetch_pages": {
                    "type": "boolean",
                    "description": "true turns on env.HYPAMOON.fetchPage(url) for this app (off by default; needs auth). It fetches one public web page for the signed-in user, for example to import a recipe or read a product page from any site, without listing that site in allowed_hosts: const page = await env.HYPAMOON.fetchPage(url) gives { url, status, contentType, body, truncated }, with url the final address after redirects. Only GET, http and https, text, HTML or JSON (also JSON-LD), at most 2 MB (truncated: true when cut off) and 10 seconds, at most 3 redirects. Private addresses and Hypamoon hosts are blocked. No cookies or x-hypamoon headers are sent. Each signed-in user can fetch 20 pages per hour, and the fetches count against the app's outbound budget. A refusal throws an Error with .status: 401 not signed in, 403 blocked address, 415 not text, 429 limit reached, 501 off for this app, 502 or 504 the page failed or was too slow. On an update, leave it out to keep the current setting; false turns it off."
                  },
                  "keep_existing_modules": {
                    "type": "boolean",
                    "description": "On an update: true keeps every running module you do not send, and replaces or adds only the modules you send (like keep_existing on deploy_site)."
                  }
                },
                "required": [
                  "site_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                "name": "Contact form",
                "main_module": "index.js",
                "modules": [
                  {
                    "name": "index.js",
                    "content": "export default {\n  async fetch(request, env) {\n    const url = new URL(request.url);\n    if (url.pathname === \"/api/contact\" && request.method === \"POST\") {\n      const form = await request.formData();\n      const city = String(form.get(\"city\") || \"Delft\");\n      // The secret weather_key is added by Hypamoon; this code never sees it.\n      const weather = await fetch(\"https://api.weatherapi.com/v1/current.json?q=\" + encodeURIComponent(city), {\n        headers: { \"x-hypamoon-secret\": \"weather_key\" },\n      }).then((r) => r.json());\n      // The project's data: Hypamoon adds the app's own key.\n      await fetch(env.HYPAMOON_API + \"/collections/contact_requests/documents\", {\n        method: \"POST\",\n        headers: { \"content-type\": \"application/json\" },\n        body: JSON.stringify({ data: { name: form.get(\"name\"), email: form.get(\"email\"), city } }),\n      });\n      return Response.json({ ok: true, temperature_c: weather.current?.temp_c ?? null });\n    }\n    return new Response(\"Not found\", { status: 404 });\n  },\n  async scheduled(event, env) {\n    // Runs on the schedule, for example a daily summary.\n  },\n};\n"
                  }
                ],
                "allowed_hosts": [
                  "api.weatherapi.com"
                ],
                "secrets": [
                  "weather_key"
                ],
                "schedules": [
                  {
                    "cron": "0 7 * * *"
                  }
                ],
                "vars": {
                  "DEFAULT_CITY": "Delft"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A new version is live.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An app: a site with server code on /api."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Contact form",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://contact-demo.hypamoon.app",
                  "api_url": "https://contact-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 2,
                  "allowed_hosts": [
                    "api.weatherapi.com"
                  ],
                  "secrets": [
                    "weather_key"
                  ],
                  "schedules": [
                    {
                      "cron": "0 7 * * *"
                    }
                  ],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "The app is live.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An app: a site with server code on /api."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Contact form",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://contact-demo.hypamoon.app",
                  "api_url": "https://contact-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 1,
                  "allowed_hosts": [
                    "api.weatherapi.com"
                  ],
                  "secrets": [
                    "weather_key"
                  ],
                  "schedules": [
                    {
                      "cron": "0 7 * * *"
                    }
                  ],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z"
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the deploy waits for another owner or builder. The running code has not changed yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "app_deploy",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 1,
                  "bytes": 1834,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "Error: app_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "app_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/app_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, app_blocked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "app_blocked"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/app_blocked"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, deploy_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "deploy_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/deploy_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}": {
      "get": {
        "operationId": "getApp",
        "tags": [
          "Apps"
        ],
        "summary": "Get an app",
        "description": "One app with the settings of the running version (main_module, allowed_hosts, secrets by name only, schedules, vars with their values, auth, database, fetch_pages), its stored versions and its most recent errors. A deploy that leaves a setting out keeps the value shown here.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The app.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    },
                    "main_module": {
                      "type": "string",
                      "description": "The main module of the running version."
                    },
                    "vars": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      },
                      "description": "The plain settings in env of the running version, with their values. Secrets are not here: secrets lists only their names."
                    },
                    "fetch_pages": {
                      "type": "boolean",
                      "description": "Whether env.HYPAMOON.fetchPage is on in the running version."
                    },
                    "versions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "version": {
                            "type": "integer"
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "version",
                          "bytes",
                          "created_at"
                        ],
                        "additionalProperties": false
                      },
                      "description": "The kept versions, newest first (at most 10)."
                    },
                    "recent_errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "at": {
                            "type": "string"
                          },
                          "status": {
                            "type": "integer"
                          },
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "at",
                          "status",
                          "path",
                          "message"
                        ],
                        "additionalProperties": false,
                        "description": "A recent error of the app, without personal data."
                      },
                      "description": "The newest errors (last 7 days), if the log could be read."
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at",
                    "main_module",
                    "vars",
                    "fetch_pages",
                    "versions",
                    "recent_errors"
                  ],
                  "additionalProperties": false,
                  "description": "An app with its versions and recent errors."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Contact form",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://contact-demo.hypamoon.app",
                  "api_url": "https://contact-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 2,
                  "allowed_hosts": [
                    "api.weatherapi.com"
                  ],
                  "secrets": [
                    "weather_key"
                  ],
                  "schedules": [
                    {
                      "cron": "0 7 * * *"
                    }
                  ],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z",
                  "main_module": "index.js",
                  "vars": {
                    "CITY": "Delft"
                  },
                  "fetch_pages": false,
                  "versions": [
                    {
                      "version": 2,
                      "bytes": 1834,
                      "created_at": "2026-10-06T09:30:00.000Z"
                    },
                    {
                      "version": 1,
                      "bytes": 1620,
                      "created_at": "2026-10-06T09:00:00.000Z"
                    }
                  ],
                  "recent_errors": [
                    {
                      "at": "2026-10-06T09:41:12.000Z",
                      "status": 500,
                      "path": "/api/contact",
                      "message": "TypeError: Cannot read properties of undefined (reading 'temp_c')"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteApp",
        "tags": [
          "Apps"
        ],
        "summary": "Delete an app",
        "description": "Removes the server code, its versions, its schedules and its key; within seconds, /api is an ordinary path on the site again. The site itself remains (delete_site removes it). Deleting it again returns 204 as well.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/rollback": {
      "post": {
        "operationId": "rollbackApp",
        "tags": [
          "Apps"
        ],
        "summary": "Roll an app back",
        "description": "Makes a stored version live again, with the settings it had (allowed hosts, secrets, schedules, vars). Without version: the version before the current one.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "version": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The version to go back to. Leave it out for the version before the current one."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "version": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The app as it runs now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An app: a site with server code on /api."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Contact form",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://contact-demo.hypamoon.app",
                  "api_url": "https://contact-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 1,
                  "allowed_hosts": [
                    "api.weatherapi.com"
                  ],
                  "secrets": [
                    "weather_key"
                  ],
                  "schedules": [
                    {
                      "cron": "0 7 * * *"
                    }
                  ],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z"
                }
              }
            }
          },
          "202": {
            "description": "Four-eyes approval is on: the rollback waits for another owner or builder. Nothing has changed yet.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeployApproval"
                },
                "example": {
                  "object": "deploy_approval",
                  "id": "8f6c2a51-3d1e-4b7a-9c42-0e5d6f7a8b90",
                  "status": "pending",
                  "kind": "app_rollback",
                  "site_id": "3b2f9c1e-7a4d-4e8b-b5c6-1d2e3f4a5b6c",
                  "name": "Acme Plumbing",
                  "slug": "acme-plumbing",
                  "url": "https://acme-plumbing.hypamoon.app",
                  "files": 1,
                  "bytes": 1620,
                  "password_change": "none",
                  "requested_by": {
                    "user_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
                    "via": "api",
                    "app": "Claude"
                  },
                  "requested_at": "2026-10-05T10:15:00Z",
                  "expires_at": "2026-10-12T10:15:00Z",
                  "reviewed_by": null,
                  "reviewed_at": null,
                  "reason": null,
                  "result": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/logs": {
      "get": {
        "operationId": "getAppLogs",
        "tags": [
          "Apps"
        ],
        "summary": "Read the log of an app",
        "description": "The newest requests, errors, scheduled runs and outbound calls of the last 7 days, newest first. Headers, cookies, query strings and bodies are never logged; in paths, parts that look like an email address, a long number or a token are replaced by :x. New lines can take up to a minute to appear.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Lines, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "errors_only",
            "in": "query",
            "description": "Only errors.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The log.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "at": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "request",
                              "error",
                              "scheduled",
                              "outbound",
                              "webhook",
                              "retry",
                              "dead_letter"
                            ],
                            "description": "webhook: a queued request delivered to the app. retry: an attempt failed and another is planned. dead_letter: given up after 6 attempts."
                          },
                          "method": {
                            "type": "string"
                          },
                          "path": {
                            "type": "string",
                            "description": "The path without query, with parts that look like an email address, a long number or a token replaced by :x. For outbound: the host."
                          },
                          "status": {
                            "type": "integer"
                          },
                          "duration_ms": {
                            "type": "integer"
                          },
                          "message": {
                            "type": "string",
                            "description": "For an error: a short message without personal data or keys."
                          }
                        },
                        "required": [
                          "at",
                          "kind",
                          "method",
                          "path",
                          "status",
                          "duration_ms",
                          "message"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "at": "2026-10-06T09:41:12.000Z",
                      "kind": "request",
                      "method": "POST",
                      "path": "/api/contact",
                      "status": 200,
                      "duration_ms": 182,
                      "message": ""
                    },
                    {
                      "at": "2026-10-06T09:41:12.000Z",
                      "kind": "outbound",
                      "method": "GET",
                      "path": "api.weatherapi.com",
                      "status": 200,
                      "duration_ms": 95,
                      "message": ""
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/users": {
      "get": {
        "operationId": "listAppUsers",
        "tags": [
          "Apps"
        ],
        "summary": "List the users of an app",
        "description": "The people who may sign in to the app, with their role and when they last signed in. Hypamoon keeps only their email address and name.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The users.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The person's id: sub in x-hypamoon-user."
                          },
                          "email": {
                            "type": "string"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "role": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "last_sign_in_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "email",
                          "name",
                          "role",
                          "created_at",
                          "last_sign_in_at"
                        ],
                        "additionalProperties": false,
                        "description": "Someone who may sign in to the app."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                      "email": "sam@example.com",
                      "name": "Sam",
                      "role": "member",
                      "created_at": "2026-10-06T10:00:00.000Z",
                      "last_sign_in_at": "2026-10-06T10:05:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "inviteAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Invite someone to an app",
        "description": "Lets this person sign in to the app with the given role, and emails them a sign-in link (unless send_email is false). The app needs auth (deploy_app with auth). With signup invite (the default) only invited people can sign in. Invitations with an email are limited per app to 20 per hour and 100 per day, and the same address once per 10 minutes (429 with Retry-After).",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Their email address."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "Their name, shown to the app."
                  },
                  "role": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "One of the app's roles. Defaults to default_role."
                  },
                  "send_email": {
                    "type": "boolean",
                    "description": "Send them a sign-in link now (default true)."
                  }
                },
                "required": [
                  "email"
                ],
                "additionalProperties": false
              },
              "example": {
                "email": "sam@example.com",
                "name": "Sam",
                "role": "member"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invited.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The person's id: sub in x-hypamoon-user."
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_sign_in_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "name",
                    "role",
                    "created_at",
                    "last_sign_in_at"
                  ],
                  "additionalProperties": false,
                  "description": "Someone who may sign in to the app."
                },
                "example": {
                  "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "email": "sam@example.com",
                  "name": "Sam",
                  "role": "member",
                  "created_at": "2026-10-06T10:00:00.000Z",
                  "last_sign_in_at": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/mails": {
      "get": {
        "operationId": "listAppMails",
        "tags": [
          "Apps"
        ],
        "summary": "List the emails an app sent",
        "description": "The sign-in links, codes, invitations and account emails the app sent, newest first, with their delivery status: queued, sent (accepted by the mail provider), delivered (accepted by the recipient's mail server), bounced, complained or failed, and the reason. Recipients are masked (a***@example.com); links and codes are never shown. Use it when someone saw \"Check your email\" and nothing arrived: delivered means it is in their spam, junk or quarantine.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Emails, 1 to 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The emails.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "sent_at": {
                            "type": "string",
                            "description": "When the app sent it (UTC)."
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "sign_in_link",
                              "sign_in_code",
                              "invitation",
                              "approval",
                              "confirmation",
                              "account"
                            ]
                          },
                          "to": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient, masked (a***@example.com). Null for emails from before 13 October 2026."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why: a bounce message, a delay, the daily limit, or an error of the mail provider."
                          },
                          "meaning": {
                            "type": "string",
                            "description": "What the status means and what to do."
                          },
                          "status_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "provider_ref": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The first characters of the id at the mail provider, for support."
                          }
                        },
                        "required": [
                          "sent_at",
                          "kind",
                          "to",
                          "status",
                          "reason",
                          "meaning",
                          "status_at",
                          "provider_ref"
                        ],
                        "additionalProperties": false,
                        "description": "An email the app sent: a sign-in link or code, an invitation, an approval or an account email."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "sent_at": "2026-10-13T12:38:30.723Z",
                      "kind": "sign_in_link",
                      "to": "a***@example.com",
                      "status": "delivered",
                      "reason": null,
                      "meaning": "Accepted by the recipient's mail server. If it is not in the inbox, it is in spam, junk or quarantine.",
                      "status_at": "2026-10-13T12:40:02.000Z",
                      "provider_ref": "01a13545"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/users/import": {
      "post": {
        "operationId": "importAppUsers",
        "tags": [
          "Apps"
        ],
        "summary": "Import users from a collection",
        "description": "Makes the people in a collection (default wp_users, from the Hypamoon Connect plugin for WordPress) users of the app, with role_map from WordPress roles to app roles. No passwords: people sign in with a link or code by email. send_email false (the default) adds them silently; true emails each new person an invitation, within the invitation limits (20 per hour and 100 per day per app; the rest is reported as remaining with retry_after). Idempotent on the WordPress id and the email address: people who can already sign in are left as they are. The app user id is written back into each document (hypamoon_app_users). dry_run shows the plan without changing anything.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "collection": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "The collection with the people, default wp_users (from the Hypamoon Connect plugin). Each document needs email; name comes from display_name, name or first_name and last_name; roles from roles (a list) or role."
                  },
                  "role_map": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_-]{0,31}$"
                    },
                    "propertyNames": {
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "description": "WordPress role to app role, like { \"administrator\": \"admin\", \"opdrachtgever\": \"opdrachtgever\", \"kandidaat\": \"kandidaat\" }. A person gets the app role of the first of their roles that is in the map. Without role_map, a WordPress role with the same name as an app role is used."
                  },
                  "default_role": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "The app role for people whose roles are not in role_map. Leave it out (or null) to skip them; without role_map the app's default_role is used."
                  },
                  "send_email": {
                    "type": "boolean",
                    "description": "false (the default): add them silently, no email; they sign in later with a link or code they request on the site. true: email each new person an invitation (counts toward the app's invitation limits and the email budget)."
                  },
                  "invite_existing": {
                    "type": "boolean",
                    "description": "Only with send_email true: also email people who were added earlier (for example silently) and never signed in. Default false, so running the import again sends nothing twice."
                  },
                  "dry_run": {
                    "type": "boolean",
                    "description": "true: change nothing, only show who would be added with which role."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "collection": "wp_users",
                "role_map": {
                  "administrator": "admin",
                  "opdrachtgever": "opdrachtgever",
                  "kandidaat": "kandidaat"
                },
                "send_email": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What happened per person.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "collection": {
                      "type": "string"
                    },
                    "dry_run": {
                      "type": "boolean"
                    },
                    "send_email": {
                      "type": "boolean"
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "found": {
                          "type": "integer"
                        },
                        "created": {
                          "type": "integer"
                        },
                        "invited": {
                          "type": "integer"
                        },
                        "existing": {
                          "type": "integer"
                        },
                        "skipped": {
                          "type": "integer"
                        },
                        "remaining": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "found",
                        "created",
                        "invited",
                        "existing",
                        "skipped",
                        "remaining"
                      ],
                      "additionalProperties": false,
                      "description": "created: added now (invited: of those, emailed). existing: could already sign in (matched on WordPress id or email; their role is not changed). remaining: not done yet because of a limit; call again."
                    },
                    "users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "wp_id": {
                            "anyOf": [
                              {
                                "anyOf": [
                                  {
                                    "type": "number"
                                  },
                                  {
                                    "type": "string"
                                  }
                                ]
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app user id (sub in x-hypamoon-user): use it as the owner in the app's tables."
                          },
                          "email": {
                            "type": "string",
                            "description": "Masked, like s***@example.com."
                          },
                          "role": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "created",
                              "invited",
                              "existing",
                              "skipped",
                              "would_create",
                              "remaining"
                            ]
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "wp_id",
                          "user_id",
                          "email",
                          "role",
                          "status"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Per person what happened, in the order of the collection."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before the remaining invitations can go out."
                    },
                    "next_step": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "app_id",
                    "collection",
                    "dry_run",
                    "send_email",
                    "summary",
                    "users",
                    "next_step"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "app_id": "8d0f4a1e-2b3c-4d5e-8f60-718293a4b5c6",
                  "collection": "wp_users",
                  "dry_run": false,
                  "send_email": false,
                  "summary": {
                    "found": 2,
                    "created": 1,
                    "invited": 0,
                    "existing": 1,
                    "skipped": 0,
                    "remaining": 0
                  },
                  "users": [
                    {
                      "wp_id": 12,
                      "user_id": "3f1e2d3c-4b5a-4c6d-9e8f-0a1b2c3d4e5f",
                      "email": "s***@example.com",
                      "role": "opdrachtgever",
                      "status": "created"
                    },
                    {
                      "wp_id": 1,
                      "user_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "email": "a***@example.com",
                      "role": "admin",
                      "status": "existing"
                    }
                  ],
                  "next_step": "1 added silently, without email, 1 could already sign in, 0 skipped."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/users/{userId}": {
      "patch": {
        "operationId": "setAppUserRole",
        "tags": [
          "Apps"
        ],
        "summary": "Change the role of a user",
        "description": "Sets one of the app's roles. The app sees the new role in x-hypamoon-user within seconds.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "The id of the user (sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "One of the app's roles."
                  }
                },
                "required": [
                  "role"
                ],
                "additionalProperties": false
              },
              "example": {
                "role": "admin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The user with the new role.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The person's id: sub in x-hypamoon-user."
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_sign_in_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "name",
                    "role",
                    "created_at",
                    "last_sign_in_at"
                  ],
                  "additionalProperties": false,
                  "description": "Someone who may sign in to the app."
                },
                "example": {
                  "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "email": "sam@example.com",
                  "name": "Sam",
                  "role": "admin",
                  "created_at": "2026-10-06T10:00:00.000Z",
                  "last_sign_in_at": "2026-10-06T10:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "removeAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Remove a user from an app",
        "description": "The person can no longer sign in; open sessions stop within seconds. Without another app in the project their email address and name are deleted too. With delete_data=true everything of this person in the app goes as well (like DELETE /apps/{id}/users/{userId}/data); ask the owner of the app which they want.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "The id of the user (sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "delete_data",
            "in": "query",
            "description": "Also delete their rows in per-user tables, their files and their passkeys. Without it, their data stays.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed, with delete_data=true: what was deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Per per-user table, the rows deleted."
                        },
                        "files": {
                          "type": "integer"
                        },
                        "sessions": {
                          "type": "integer",
                          "description": "Open sessions; they end within seconds."
                        },
                        "passkeys": {
                          "type": "integer"
                        },
                        "membership": {
                          "type": "boolean"
                        },
                        "account": {
                          "type": "boolean",
                          "description": "true: the email address and name are gone too (no other app in this project knew the person)."
                        }
                      },
                      "required": [
                        "rows",
                        "files",
                        "sessions",
                        "passkeys",
                        "membership",
                        "account"
                      ],
                      "additionalProperties": false
                    },
                    "app_tables": {
                      "type": "string",
                      "enum": [
                        "included",
                        "not_included",
                        "no_database"
                      ],
                      "description": "included: the per-user tables (set_table_owner_column) were handled; unmarked_tables may still hold data. not_included: the app has no per-user tables, so no row of the app's own database was exported or deleted; check those tables yourself. no_database: the app has no database."
                    },
                    "unmarked_tables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Tables that are not marked per-user: Hypamoon cannot tell which rows there belong to this user, so they may still hold data about them."
                    },
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "app_id",
                    "user_id",
                    "deleted",
                    "app_tables",
                    "unmarked_tables",
                    "note"
                  ],
                  "additionalProperties": false,
                  "description": "What was deleted."
                },
                "example": {
                  "app_id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "deleted": {
                    "rows": {
                      "items": 12
                    },
                    "files": 12,
                    "sessions": 1,
                    "passkeys": 1,
                    "membership": true,
                    "account": true
                  },
                  "app_tables": "included",
                  "unmarked_tables": [],
                  "note": null
                }
              }
            }
          },
          "204": {
            "description": "Removed; their data stays.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/users/{userId}/export": {
      "post": {
        "operationId": "exportAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Export everything of one user (GDPR)",
        "description": "Everything of one user of the app as JSON: what Hypamoon keeps (account, membership, passkeys without the keys), every row in the per-user tables (set with set_table_owner_column) and every file of theirs with a download link for 15 minutes. unmarked_tables lists the tables that are not per-user: Hypamoon cannot tell which rows there are theirs, so check those yourself. app_tables not_included: the app has no per-user tables at all, so no row of its database is in the export.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "The id of the user (sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "exported_at": {
                      "type": "string"
                    },
                    "account": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "email",
                        "name",
                        "created_at"
                      ],
                      "additionalProperties": false,
                      "description": "What Hypamoon keeps about the person; null if the account is already gone."
                    },
                    "membership": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "role": {
                          "type": "string"
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "last_sign_in_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "active_sessions": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "role",
                        "created_at",
                        "last_sign_in_at",
                        "active_sessions"
                      ],
                      "additionalProperties": false,
                      "description": "Their place in this app; null if they were removed from it."
                    },
                    "passkeys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "rp_id": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "last_used_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "name",
                          "rp_id",
                          "created_at",
                          "last_used_at"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Their passkeys for this app, without the keys themselves."
                    },
                    "tables": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "owner_column": {
                            "type": "string"
                          },
                          "rows": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "additionalProperties": {}
                            }
                          },
                          "truncated": {
                            "type": "boolean",
                            "description": "More than 10,000 rows: only the first 10,000 are here."
                          }
                        },
                        "required": [
                          "owner_column",
                          "rows",
                          "truncated"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Per per-user table, every row of this user."
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "ready"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "download_url": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Valid for 15 minutes, without signing in; null for a file that never finished uploading."
                          },
                          "expires_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "content_type",
                          "bytes",
                          "status",
                          "created_at",
                          "download_url",
                          "expires_at"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Every file of this user."
                    },
                    "app_tables": {
                      "type": "string",
                      "enum": [
                        "included",
                        "not_included",
                        "no_database"
                      ],
                      "description": "included: the per-user tables (set_table_owner_column) were handled; unmarked_tables may still hold data. not_included: the app has no per-user tables, so no row of the app's own database was exported or deleted; check those tables yourself. no_database: the app has no database."
                    },
                    "unmarked_tables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Tables that are not marked per-user: Hypamoon cannot tell which rows there belong to this user, so they may still hold data about them."
                    },
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "app_id",
                    "user_id",
                    "exported_at",
                    "account",
                    "membership",
                    "passkeys",
                    "tables",
                    "files",
                    "app_tables",
                    "unmarked_tables",
                    "note"
                  ],
                  "additionalProperties": false,
                  "description": "Everything about one user of an app, as JSON, with temporary download links for their files."
                },
                "example": {
                  "app_id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "exported_at": "2026-10-07T12:00:00.000Z",
                  "account": {
                    "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                    "email": "sam@example.com",
                    "name": "Sam",
                    "created_at": "2026-10-06T10:00:00.000Z"
                  },
                  "membership": {
                    "role": "member",
                    "created_at": "2026-10-06T10:00:00.000Z",
                    "last_sign_in_at": "2026-10-06T10:05:00.000Z",
                    "active_sessions": 1
                  },
                  "passkeys": [
                    {
                      "name": "iPhone",
                      "rp_id": "wardrobe-demo.hypamoon.app",
                      "created_at": "2026-10-06T10:06:00.000Z",
                      "last_used_at": "2026-10-07T08:00:00.000Z"
                    }
                  ],
                  "tables": {
                    "items": {
                      "owner_column": "user_id",
                      "rows": [
                        {
                          "id": "c1",
                          "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                          "name": "Blue coat",
                          "photo_file_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                          "created_at": "2026-10-06T10:10:00.000Z"
                        }
                      ],
                      "truncated": false
                    }
                  },
                  "files": [
                    {
                      "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                      "name": "coat.jpg",
                      "content_type": "image/jpeg",
                      "bytes": 48213,
                      "status": "ready",
                      "created_at": "2026-10-06T10:10:00.000Z",
                      "download_url": "https://wardrobe-demo.hypamoon.app/_hypamoon/files/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9?t=eyJvIjoi...",
                      "expires_at": "2026-10-07T12:15:00.000Z"
                    }
                  ],
                  "app_tables": "included",
                  "unmarked_tables": [
                    "outfits_shared"
                  ],
                  "note": "These tables are not marked as per-user, so Hypamoon cannot tell which rows belong to this user. They may still hold data about them: check them yourself (or mark them with set_table_owner_column)."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/users/{userId}/data": {
      "delete": {
        "operationId": "deleteAppUserData",
        "tags": [
          "Apps"
        ],
        "summary": "Delete everything of one user (GDPR)",
        "description": "Permanently deletes everything of one user of the app: their rows in the per-user tables, their files, their sessions (signed out within seconds), their membership and passkeys, and without another app in the project their email address and name. unmarked_tables lists the tables that are not per-user and may still hold data about them. app_tables not_included: the app has no per-user tables at all, so no row of its database was deleted. Safe to repeat.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "The id of the user (sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What was deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Per per-user table, the rows deleted."
                        },
                        "files": {
                          "type": "integer"
                        },
                        "sessions": {
                          "type": "integer",
                          "description": "Open sessions; they end within seconds."
                        },
                        "passkeys": {
                          "type": "integer"
                        },
                        "membership": {
                          "type": "boolean"
                        },
                        "account": {
                          "type": "boolean",
                          "description": "true: the email address and name are gone too (no other app in this project knew the person)."
                        }
                      },
                      "required": [
                        "rows",
                        "files",
                        "sessions",
                        "passkeys",
                        "membership",
                        "account"
                      ],
                      "additionalProperties": false
                    },
                    "app_tables": {
                      "type": "string",
                      "enum": [
                        "included",
                        "not_included",
                        "no_database"
                      ],
                      "description": "included: the per-user tables (set_table_owner_column) were handled; unmarked_tables may still hold data. not_included: the app has no per-user tables, so no row of the app's own database was exported or deleted; check those tables yourself. no_database: the app has no database."
                    },
                    "unmarked_tables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Tables that are not marked per-user: Hypamoon cannot tell which rows there belong to this user, so they may still hold data about them."
                    },
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "app_id",
                    "user_id",
                    "deleted",
                    "app_tables",
                    "unmarked_tables",
                    "note"
                  ],
                  "additionalProperties": false,
                  "description": "What was deleted."
                },
                "example": {
                  "app_id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "deleted": {
                    "rows": {
                      "items": 12
                    },
                    "files": 12,
                    "sessions": 1,
                    "passkeys": 1,
                    "membership": true,
                    "account": true
                  },
                  "app_tables": "included",
                  "unmarked_tables": [],
                  "note": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/tables/{table}/owner": {
      "put": {
        "operationId": "setTableOwner",
        "tags": [
          "Apps"
        ],
        "summary": "Make a table private per user",
        "description": "Marks a table of the app's database as per-user: column holds the owner (the user's id, sub in x-hypamoon-user). From then on the app reads and writes it only with env.DB.mine(table) (select, get, count, insert, update, delete), always for the signed-in user; plain SQL that names the table is refused, and so are migrations that would read it around that (views, copies). admin_role lets users with that role use all rows with env.DB.mine(table, { all: true }). The app needs auth and database; the running version picks it up within seconds.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "description": "The name of a table in the app's database.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "column": {
                    "type": "string",
                    "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
                    "description": "The column that holds the owner: the user's id (sub from x-hypamoon-user). Defaults to user_id. It must exist; make it TEXT NOT NULL with an index."
                  },
                  "admin_role": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "Optional. A role of the app (like admin) whose users may use all rows with env.DB.mine(table, { all: true }). Leave it out and nobody sees other users' rows, admins included."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "column": "user_id",
                "admin_role": "admin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The app, with database.per_user_tables.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An app: a site with server code on /api."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Wardrobe",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://wardrobe-demo.hypamoon.app",
                  "api_url": "https://wardrobe-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 2,
                  "allowed_hosts": [],
                  "secrets": [],
                  "schedules": [],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z",
                  "auth": {
                    "signup": "invite",
                    "roles": [
                      "admin",
                      "member"
                    ],
                    "default_role": "member",
                    "google": false,
                    "login_url": "https://wardrobe-demo.hypamoon.app/_hypamoon/auth/login"
                  },
                  "database": {
                    "bytes": 24576,
                    "full": false,
                    "per_user_tables": [
                      {
                        "table": "items",
                        "column": "user_id",
                        "admin_role": "admin"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "removeTableOwner",
        "tags": [
          "Apps"
        ],
        "summary": "Make a per-user table shared again",
        "description": "Removes the per-user marking: the app can use the table with plain SQL again, and nothing keeps users' rows apart any more. The rows stay.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "table",
            "in": "path",
            "required": true,
            "description": "The name of a table in the app's database.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The app.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "url": {
                      "type": "string",
                      "description": "The site; the app answers on url + /api."
                    },
                    "api_url": {
                      "type": "string",
                      "description": "Where the app's /api starts."
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "offline"
                      ],
                      "description": "offline: Hypamoon took the site offline; the app does not run."
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "allowed_hosts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "schedules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cron": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "cron"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "cpu_ms": {
                          "type": "integer"
                        },
                        "subrequests": {
                          "type": "integer"
                        },
                        "outbound_per_minute": {
                          "type": "integer"
                        },
                        "outbound_per_day": {
                          "type": "integer"
                        },
                        "bundle_bytes": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "cpu_ms",
                        "subrequests",
                        "outbound_per_minute",
                        "outbound_per_day",
                        "bundle_bytes"
                      ],
                      "additionalProperties": false,
                      "description": "Per request: CPU time and fetch calls. Per app: outbound requests per minute and per day."
                    },
                    "auth": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "signup": {
                          "type": "string",
                          "enum": [
                            "invite",
                            "open",
                            "approval",
                            "domain"
                          ]
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "default_role": {
                          "type": "string"
                        },
                        "google": {
                          "type": "boolean"
                        },
                        "protect": {
                          "type": "string",
                          "enum": [
                            "site",
                            "none"
                          ]
                        },
                        "session_days": {
                          "type": "integer"
                        },
                        "admin_session_days": {
                          "type": "integer"
                        },
                        "allowed_domains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "buyers": {
                          "type": "boolean"
                        },
                        "groups": {
                          "type": "boolean"
                        },
                        "login_url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "signup",
                        "roles",
                        "default_role",
                        "google",
                        "login_url"
                      ],
                      "additionalProperties": false,
                      "description": "Sign-in for the app's users; null when it is off. login_url is where people sign in."
                    },
                    "database": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "bytes": {
                          "type": "integer"
                        },
                        "full": {
                          "type": "boolean"
                        },
                        "per_user_tables": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "table": {
                                "type": "string"
                              },
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "table",
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false
                          },
                          "description": "Tables marked with set_table_owner_column: the app reads and writes them only through env.DB.mine(table), for the signed-in user."
                        }
                      },
                      "required": [
                        "bytes",
                        "full"
                      ],
                      "additionalProperties": false,
                      "description": "The app's own database (env.DB); null when it has none. full: above the plan's data limit, so the app can only read and delete until there is room."
                    },
                    "kept": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy of an existing app: the settings that were left out and kept from the running version, like \"allowed_hosts (20)\", \"vars (2)\", \"database\", \"auth\"."
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Only in the answer to a deploy: advice about the app's data, never a block. For example that export and delete per user won't cover the data (no table marked with set_table_owner_column), or tables that look like backups or copies. Tell the user in plain words and offer to fix it."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "site_id",
                    "url",
                    "api_url",
                    "mode",
                    "status",
                    "version",
                    "allowed_hosts",
                    "secrets",
                    "schedules",
                    "limits",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false,
                  "description": "An app: a site with server code on /api."
                },
                "example": {
                  "id": "9d2e4f6a-1b3c-4d5e-8f70-a1b2c3d4e5f6",
                  "name": "Wardrobe",
                  "site_id": "5b0c7c2e-8f1d-4a3b-9c6e-2d4f5a6b7c8d",
                  "url": "https://wardrobe-demo.hypamoon.app",
                  "api_url": "https://wardrobe-demo.hypamoon.app/api",
                  "mode": "test",
                  "status": "active",
                  "version": 2,
                  "allowed_hosts": [],
                  "secrets": [],
                  "schedules": [],
                  "limits": {
                    "cpu_ms": 10,
                    "subrequests": 10,
                    "outbound_per_minute": 60,
                    "outbound_per_day": 2000,
                    "bundle_bytes": 1048576
                  },
                  "created_at": "2026-10-06T09:00:00.000Z",
                  "updated_at": "2026-10-06T09:30:00.000Z",
                  "auth": {
                    "signup": "invite",
                    "roles": [
                      "admin",
                      "member"
                    ],
                    "default_role": "member",
                    "google": false,
                    "login_url": "https://wardrobe-demo.hypamoon.app/_hypamoon/auth/login"
                  },
                  "database": {
                    "bytes": 24576,
                    "full": false,
                    "per_user_tables": []
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/files": {
      "get": {
        "operationId": "listAppFiles",
        "tags": [
          "Apps"
        ],
        "summary": "List the files of an app",
        "description": "Files the app's users uploaded through the app, newest first, with the total size. The app itself uploads and downloads through /app-files.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Files, 1 to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The files.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "ready"
                            ],
                            "description": "pending: the upload link was made but nothing arrived yet."
                          },
                          "user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The signed-in person the file belongs to (the app forwarded their signed headers), or null for a file of the app itself."
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "content_type",
                          "bytes",
                          "status",
                          "user_id",
                          "created_at"
                        ],
                        "additionalProperties": false,
                        "description": "A file uploaded to the app by one of its users."
                      }
                    },
                    "bytes": {
                      "type": "integer",
                      "description": "All files of this app together."
                    }
                  },
                  "required": [
                    "data",
                    "bytes"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                      "name": "receipt.pdf",
                      "content_type": "application/pdf",
                      "bytes": 48213,
                      "status": "ready",
                      "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                      "created_at": "2026-10-06T10:10:00.000Z"
                    }
                  ],
                  "bytes": 48213
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/files/{fileId}": {
      "delete": {
        "operationId": "deleteAppFile",
        "tags": [
          "Apps"
        ],
        "summary": "Delete a file from an app",
        "description": "Deletes the file permanently, including from storage.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/migrations": {
      "get": {
        "operationId": "listAppMigrations",
        "tags": [
          "Apps"
        ],
        "summary": "List the migrations of an app",
        "description": "The migrations applied to the app's database, oldest first, and its size.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The migrations.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "applied": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Applied now."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Already applied before."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "sha256": {
                            "type": "string"
                          },
                          "statements": {
                            "type": "integer"
                          },
                          "applied_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "name",
                          "sha256",
                          "statements",
                          "applied_at"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Every migration of this app, oldest first."
                    },
                    "database_bytes": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "applied",
                    "skipped",
                    "data",
                    "database_bytes"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "applied": [],
                  "skipped": [],
                  "data": [
                    {
                      "name": "0001_init.sql",
                      "sha256": "9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f",
                      "statements": 2,
                      "applied_at": "2026-10-06T10:00:00.000Z"
                    }
                  ],
                  "database_bytes": 24576
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "applyAppMigration",
        "tags": [
          "Apps"
        ],
        "summary": "Apply migrations to the database of an app",
        "description": "Runs new SQL migrations on the app's own database (env.DB), in order, each as one batch: all or nothing. A migration that was already applied with the same content is skipped; a new name must sort after the last applied one. Only DDL and DML on that database: CREATE TABLE, CREATE INDEX, CREATE VIEW, ALTER, DROP, INSERT, UPDATE, DELETE. No ATTACH, PRAGMA, VACUUM, triggers or transactions. The app needs database: true.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "migrations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "pattern": "^[A-Za-z0-9_.-]{1,96}\\.sql$",
                          "description": "A file name that sorts in order, like 0001_init.sql."
                        },
                        "sql": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 524288,
                          "description": "SQLite DDL and DML: CREATE TABLE, CREATE INDEX, ALTER TABLE, INSERT, UPDATE, DELETE. No ATTACH, PRAGMA, VACUUM, triggers or transactions."
                        }
                      },
                      "required": [
                        "name",
                        "sql"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "New migrations in order. Ones already applied (same name and content) are skipped."
                  }
                },
                "required": [
                  "migrations"
                ],
                "additionalProperties": false
              },
              "example": {
                "migrations": [
                  {
                    "name": "0001_init.sql",
                    "sql": "CREATE TABLE expenses (id TEXT PRIMARY KEY, user_id TEXT NOT NULL, amount_cents INTEGER NOT NULL, note TEXT, file_id TEXT, created_at TEXT NOT NULL);\nCREATE INDEX expenses_user ON expenses (user_id, created_at);"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied (or already applied).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "applied": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Applied now."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Already applied before."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "sha256": {
                            "type": "string"
                          },
                          "statements": {
                            "type": "integer"
                          },
                          "applied_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "name",
                          "sha256",
                          "statements",
                          "applied_at"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Every migration of this app, oldest first."
                    },
                    "database_bytes": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "applied",
                    "skipped",
                    "data",
                    "database_bytes"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "applied": [
                    "0001_init.sql"
                  ],
                  "skipped": [],
                  "data": [
                    {
                      "name": "0001_init.sql",
                      "sha256": "9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f",
                      "statements": 2,
                      "applied_at": "2026-10-06T10:00:00.000Z"
                    }
                  ],
                  "database_bytes": 24576
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict, plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict",
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict",
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, migration_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "migration_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/migration_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/schema": {
      "get": {
        "operationId": "getAppSchema",
        "tags": [
          "Apps"
        ],
        "summary": "Read the schema of an app's database",
        "description": "Read-only. The tables of the app's own database (env.DB) with their columns, indexes, number of rows and whether they are per-user (set_table_owner_column), the views, the applied migrations and a name for the next one. Use it to see where a build stands before you change the tables.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The schema.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app_id": {
                      "type": "string"
                    },
                    "database": {
                      "type": "boolean",
                      "const": true
                    },
                    "tables": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "columns": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "name": {
                                  "type": "string"
                                },
                                "type": {
                                  "type": "string"
                                },
                                "not_null": {
                                  "type": "boolean"
                                },
                                "primary_key": {
                                  "type": "boolean"
                                },
                                "default": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "type",
                                "not_null",
                                "primary_key",
                                "default"
                              ],
                              "additionalProperties": false
                            }
                          },
                          "indexes": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "name": {
                                  "type": "string"
                                },
                                "unique": {
                                  "type": "boolean"
                                },
                                "sql": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "unique",
                                "sql"
                              ],
                              "additionalProperties": false
                            }
                          },
                          "rows": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "The number of rows, or null when the database has too many tables to count."
                          },
                          "per_user": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "column": {
                                "type": "string"
                              },
                              "admin_role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "column",
                              "admin_role"
                            ],
                            "additionalProperties": false,
                            "description": "Set with set_table_owner_column: the code reads it with env.DB.mine(table)."
                          },
                          "sql": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "name",
                          "columns",
                          "indexes",
                          "rows",
                          "per_user",
                          "sql"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "views": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "sql": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "name",
                          "sql"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "migrations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "applied_at": {
                            "type": "string"
                          },
                          "statements": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "name",
                          "applied_at",
                          "statements"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Applied migrations, oldest first."
                    },
                    "next_migration_name": {
                      "type": "string",
                      "description": "A name that sorts after the last applied migration, like 0003_next.sql; replace next with what it does."
                    },
                    "database_bytes": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "app_id",
                    "database",
                    "tables",
                    "views",
                    "migrations",
                    "next_migration_name",
                    "database_bytes"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "app_id": "4a7ff9fa-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "database": true,
                  "tables": [
                    {
                      "name": "recipes",
                      "columns": [
                        {
                          "name": "id",
                          "type": "TEXT",
                          "not_null": false,
                          "primary_key": true,
                          "default": null
                        },
                        {
                          "name": "user_id",
                          "type": "TEXT",
                          "not_null": true,
                          "primary_key": false,
                          "default": null
                        },
                        {
                          "name": "title",
                          "type": "TEXT",
                          "not_null": true,
                          "primary_key": false,
                          "default": null
                        },
                        {
                          "name": "ingredients",
                          "type": "TEXT",
                          "not_null": false,
                          "primary_key": false,
                          "default": null
                        }
                      ],
                      "indexes": [
                        {
                          "name": "recipes_user",
                          "unique": false,
                          "sql": "CREATE INDEX recipes_user ON recipes (user_id, created_at)"
                        }
                      ],
                      "rows": 12,
                      "per_user": {
                        "column": "user_id",
                        "admin_role": "admin"
                      },
                      "sql": "CREATE TABLE recipes (id TEXT PRIMARY KEY, user_id TEXT NOT NULL, title TEXT NOT NULL, ingredients TEXT)"
                    }
                  ],
                  "views": [],
                  "migrations": [
                    {
                      "name": "0001_init.sql",
                      "applied_at": "2026-10-14T10:00:00.000Z",
                      "statements": 4
                    }
                  ],
                  "next_migration_name": "0002_next.sql",
                  "database_bytes": 24576
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/code": {
      "get": {
        "operationId": "getAppCode",
        "tags": [
          "Apps"
        ],
        "summary": "Read the server code of an app",
        "description": "Read-only. main_module and the modules of the running version, or of a kept earlier version with version. Use it to continue building after a break or in a new chat: read the code, change what is needed, and deploy only the changed modules with keep_existing_modules: true. One answer holds at most about 200,000 characters of code; a module that does not fit comes without content (truncated: true) and can be read alone with module.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "version",
            "in": "query",
            "description": "A kept version (get_app lists them). Leave it out for the running version.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "module",
            "in": "query",
            "description": "Only this module, like index.js.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The code.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "app_code"
                    },
                    "app_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "version": {
                      "type": "integer",
                      "description": "The version these modules belong to."
                    },
                    "running_version": {
                      "type": "integer",
                      "description": "The version that runs now."
                    },
                    "main_module": {
                      "type": "string",
                      "description": "The module with export default { fetch }."
                    },
                    "modules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "esm",
                              "json",
                              "text"
                            ]
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "content": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The module's text. null when the answer was full: ask for it alone with ?module=."
                          },
                          "truncated": {
                            "type": "boolean",
                            "description": "true: content is missing or cut off because the answer has a size limit; ask for this module alone with ?module=."
                          }
                        },
                        "required": [
                          "name",
                          "type",
                          "bytes",
                          "content",
                          "truncated"
                        ],
                        "additionalProperties": false
                      },
                      "description": "The main module first, then the others by name."
                    }
                  },
                  "required": [
                    "object",
                    "app_id",
                    "version",
                    "running_version",
                    "main_module",
                    "modules"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "object": "app_code",
                  "app_id": "9f2b6c1e-3d4a-4b5c-8d7e-1f2a3b4c5d6e",
                  "version": 3,
                  "running_version": 3,
                  "main_module": "index.js",
                  "modules": [
                    {
                      "name": "index.js",
                      "type": "esm",
                      "bytes": 74,
                      "content": "export default { async fetch(request, env) { return new Response('hi'); } };",
                      "truncated": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/apps/{id}/query": {
      "post": {
        "operationId": "queryAppDb",
        "tags": [
          "Apps"
        ],
        "summary": "Run one read-only query on an app's database",
        "description": "Runs one SELECT on the app's own database and returns at most 50 rows, to see what is in it while building or debugging. It only reads: anything that writes or changes the schema is refused, and the query runs as a subquery with the row limit outside it. Email addresses and phone numbers in the result are masked, long text is cut and binary values show only their size. Per-user tables can be read here (the project owns the data); every query is logged in the project's activity, without its SQL. At most 120 queries per hour per project.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the app.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sql": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000,
                    "description": "One SELECT (or WITH ... SELECT). Nothing that writes or changes the schema."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "description": "Rows to return, 1 to 50 (default 20)."
                  }
                },
                "required": [
                  "sql"
                ],
                "additionalProperties": false
              },
              "example": {
                "sql": "SELECT id, title, servings FROM recipes ORDER BY created_at DESC",
                "limit": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rows.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "columns": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "row_count": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true when there were more rows than limit."
                    },
                    "per_user_tables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Per-user tables this query read."
                    },
                    "masked": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "columns",
                    "rows",
                    "row_count",
                    "limit",
                    "truncated",
                    "per_user_tables",
                    "masked"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "columns": [
                    "id",
                    "title",
                    "servings"
                  ],
                  "rows": [
                    {
                      "id": "7f1c",
                      "title": "Pasta pesto",
                      "servings": 4
                    }
                  ],
                  "row_count": 1,
                  "limit": 20,
                  "truncated": false,
                  "per_user_tables": [
                    "recipes"
                  ],
                  "masked": "Email addresses and phone numbers are masked, text longer than 300 characters is cut, and binary values show only their size."
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, query_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "query_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/query_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-files": {
      "post": {
        "operationId": "createAppUpload",
        "tags": [
          "Apps"
        ],
        "summary": "Get an upload link (from an app)",
        "description": "For an app's own server code only: fetch(env.HYPAMOON_API + \"/app-files\", { method: \"POST\", ... }) without an Authorization header; Hypamoon adds the app's key, and any other key gets 403. Returns a path on the site's own address where the browser PUTs the exact bytes, once, within 15 minutes. The type is checked against the bytes, PDFs and text go through the abuse check, and the size counts toward the files in the plan. Forward the x-hypamoon-user and x-hypamoon-user-sig headers of the request to make the file the signed-in user's own: it is stored in their own folder, only they (and users with the admin role, the first of auth.roles) can download it, and it goes with them on export or deletion. Only forward the headers; never check their signature in the code.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The file name, like receipt.pdf."
                  },
                  "content_type": {
                    "type": "string",
                    "enum": [
                      "image/png",
                      "image/jpeg",
                      "image/gif",
                      "image/webp",
                      "application/pdf",
                      "video/mp4",
                      "video/webm",
                      "audio/mpeg",
                      "audio/mp4",
                      "audio/webm",
                      "image/heic",
                      "image/heif",
                      "video/quicktime",
                      "text/plain",
                      "text/csv",
                      "application/json"
                    ],
                    "description": "The type: images (png, jpeg, gif, webp, heic, heif), pdf, video (mp4, webm, mov), audio (mp3, m4a, webm), plain text, csv or json. The bytes are checked against it."
                  },
                  "size": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The exact size in bytes (File.size in the browser)."
                  },
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Leave it out. To make the file the signed-in person's own, forward the x-hypamoon-user and x-hypamoon-user-sig headers of the request; if you send user_id, it must be that same person."
                  }
                },
                "required": [
                  "name",
                  "content_type",
                  "size"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "receipt.pdf",
                "content_type": "application/pdf",
                "size": 48213
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The upload link.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "upload_url": {
                      "type": "string",
                      "description": "PUT the bytes here from the browser, on the site's own address. Works once."
                    },
                    "method": {
                      "type": "string",
                      "const": "PUT"
                    },
                    "expires_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "upload_url",
                    "method",
                    "expires_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                  "upload_url": "/_hypamoon/files/upload?t=eyJvIjoi...",
                  "method": "PUT",
                  "expires_at": "2026-10-06T10:25:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: plan_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, site_offline.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "site_offline"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/site_offline"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: plan_limit_reached, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "plan_limit_reached",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/plan_limit_reached",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "Error: file_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "file_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/file_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "get": {
        "operationId": "listOwnAppFiles",
        "tags": [
          "Apps"
        ],
        "summary": "List the files (from an app)",
        "description": "For an app's own server code only: the uploaded files, newest first. With the signed user headers forwarded: only that user's files (a user with the admin role, the first of auth.roles, gets everyone's with all=true). Without them (the app itself, for example in a schedule): all files, optionally only those of one user.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "query",
            "description": "Only the files of this user (sub from x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Files, 1 to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "all",
            "in": "query",
            "description": "With the signed headers of a user with the admin role (the first of auth.roles): the files of every user.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The files.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "bytes": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "ready"
                            ],
                            "description": "pending: the upload link was made but nothing arrived yet."
                          },
                          "user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The signed-in person the file belongs to (the app forwarded their signed headers), or null for a file of the app itself."
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "content_type",
                          "bytes",
                          "status",
                          "user_id",
                          "created_at"
                        ],
                        "additionalProperties": false,
                        "description": "A file uploaded to the app by one of its users."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                      "name": "receipt.pdf",
                      "content_type": "application/pdf",
                      "bytes": 48213,
                      "status": "ready",
                      "user_id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                      "created_at": "2026-10-06T10:10:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-files/{id}": {
      "get": {
        "operationId": "getAppDownload",
        "tags": [
          "Apps"
        ],
        "summary": "Get a download link (from an app)",
        "description": "For an app's own server code only: a path on the site's own address that serves the file for 15 minutes. With the signed user headers forwarded, only for a file of that user (or any file for the admin role, the first of auth.roles). A file of a user only opens in a browser where that user, or a user with the admin role, is signed in. With public=true the link opens without signing in, for 15 minutes: use it only in a share route of the app, for a file the owner chose to share (like the photos of a shared recipe).",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "public",
            "in": "query",
            "description": "true: the link opens without signing in (for a share link of the app). Leave it out for a link that needs the owner's sign-in.",
            "schema": {
              "type": "boolean"
            },
            "example": "true"
          }
        ],
        "responses": {
          "200": {
            "description": "The download link.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "content_type": {
                      "type": "string"
                    },
                    "bytes": {
                      "type": "integer"
                    },
                    "download_url": {
                      "type": "string",
                      "description": "Valid for 15 minutes, on the site's own address."
                    },
                    "expires_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "content_type",
                    "bytes",
                    "download_url",
                    "expires_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
                  "name": "receipt.pdf",
                  "content_type": "application/pdf",
                  "bytes": 48213,
                  "download_url": "/_hypamoon/files/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9?t=eyJvIjoi...",
                  "expires_at": "2026-10-06T10:25:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteOwnAppFile",
        "tags": [
          "Apps"
        ],
        "summary": "Delete a file (from an app)",
        "description": "For an app's own server code only: deletes the file permanently.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the file.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-users": {
      "get": {
        "operationId": "listOwnAppUsers",
        "tags": [
          "Apps"
        ],
        "summary": "List the users (from an app, as admin)",
        "description": "For an app's own server code only, on behalf of the signed-in user: fetch(env.HYPAMOON_API + \"/app-users\", ...) without an Authorization header, forwarding the x-hypamoon-user and x-hypamoon-user-sig headers of the request unchanged. Only forward them: never check the signature in the app code yourself, Hypamoon does. Without them 401; a signed-in user without the app's first role in auth.roles (admin by default) 403. The people who may sign in to this app, with their role and when they last signed in. Use it for a people or family screen in the app, shown only to admins. In app code, env.HYPAMOON.users.list() does this call for you; it is usually answered next to the app, without a round trip to this API.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The users.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The person's id: sub in x-hypamoon-user."
                          },
                          "email": {
                            "type": "string"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "role": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "last_sign_in_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "email",
                          "name",
                          "role",
                          "created_at",
                          "last_sign_in_at"
                        ],
                        "additionalProperties": false,
                        "description": "Someone who may sign in to the app."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                      "email": "sam@example.com",
                      "name": "Sam",
                      "role": "member",
                      "created_at": "2026-10-06T10:00:00.000Z",
                      "last_sign_in_at": "2026-10-06T10:05:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "inviteOwnAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Invite someone (from an app, as admin)",
        "description": "For an app's own server code only, on behalf of the signed-in user: fetch(env.HYPAMOON_API + \"/app-users\", ...) without an Authorization header, forwarding the x-hypamoon-user and x-hypamoon-user-sig headers of the request unchanged. Only forward them: never check the signature in the app code yourself, Hypamoon does. Without them 401; a signed-in user without the app's first role in auth.roles (admin by default) 403. Lets this person sign in with the given role and emails them a sign-in link that works once and stays valid for 7 days. The email comes in the app's own style and language and names the admin who invited them. Limits per app: 20 invitations per hour and 100 per day, and the same address once per 10 minutes (429 with Retry-After). Someone who is already a user gives 409.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Their email address. Ask the admin for it; never guess one."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "Their name, shown in the app and in the list."
                  },
                  "role": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "One of the app's roles. Defaults to default_role."
                  }
                },
                "required": [
                  "email"
                ],
                "additionalProperties": false
              },
              "example": {
                "email": "sam@example.com",
                "name": "Sam",
                "role": "member"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invited, and the email is on its way.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The person's id: sub in x-hypamoon-user."
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_sign_in_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "name",
                    "role",
                    "created_at",
                    "last_sign_in_at"
                  ],
                  "additionalProperties": false,
                  "description": "Someone who may sign in to the app."
                },
                "example": {
                  "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "email": "sam@example.com",
                  "name": "Sam",
                  "role": "member",
                  "created_at": "2026-10-06T10:00:00.000Z",
                  "last_sign_in_at": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-users/{id}": {
      "get": {
        "operationId": "getOwnAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Get one user (from an app, as admin)",
        "description": "For an app's own server code only, on behalf of the signed-in user: fetch(env.HYPAMOON_API + \"/app-users\", ...) without an Authorization header, forwarding the x-hypamoon-user and x-hypamoon-user-sig headers of the request unchanged. Only forward them: never check the signature in the app code yourself, Hypamoon does. Without them 401; a signed-in user without the app's first role in auth.roles (admin by default) 403. One person who may sign in to this app, with their role. In app code, env.HYPAMOON.users.get(id) does this call for you (null when there is no such user).",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the user (their sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The user.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The person's id: sub in x-hypamoon-user."
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_sign_in_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "name",
                    "role",
                    "created_at",
                    "last_sign_in_at"
                  ],
                  "additionalProperties": false,
                  "description": "Someone who may sign in to the app."
                },
                "example": {
                  "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "email": "sam@example.com",
                  "name": "Sam",
                  "role": "member",
                  "created_at": "2026-10-06T10:00:00.000Z",
                  "last_sign_in_at": "2026-10-06T10:05:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "setOwnAppUserRole",
        "tags": [
          "Apps"
        ],
        "summary": "Change a user's role (from an app, as admin)",
        "description": "For an app's own server code only, on behalf of the signed-in user: fetch(env.HYPAMOON_API + \"/app-users\", ...) without an Authorization header, forwarding the x-hypamoon-user and x-hypamoon-user-sig headers of the request unchanged. Only forward them: never check the signature in the app code yourself, Hypamoon does. Without them 401; a signed-in user without the app's first role in auth.roles (admin by default) 403. Sets one of the app's roles; the user sees it within seconds. The last user with the first role cannot give it up (409): give someone else that role first.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the user (their sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "One of the app's roles."
                  }
                },
                "required": [
                  "role"
                ],
                "additionalProperties": false
              },
              "example": {
                "role": "admin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The user with the new role.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The person's id: sub in x-hypamoon-user."
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_sign_in_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "name",
                    "role",
                    "created_at",
                    "last_sign_in_at"
                  ],
                  "additionalProperties": false,
                  "description": "Someone who may sign in to the app."
                },
                "example": {
                  "id": "7c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
                  "email": "sam@example.com",
                  "name": "Sam",
                  "role": "admin",
                  "created_at": "2026-10-06T10:00:00.000Z",
                  "last_sign_in_at": "2026-10-06T10:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "removeOwnAppUser",
        "tags": [
          "Apps"
        ],
        "summary": "Remove a user (from an app, as admin)",
        "description": "For an app's own server code only, on behalf of the signed-in user: fetch(env.HYPAMOON_API + \"/app-users\", ...) without an Authorization header, forwarding the x-hypamoon-user and x-hypamoon-user-sig headers of the request unchanged. Only forward them: never check the signature in the app code yourself, Hypamoon does. Without them 401; a signed-in user without the app's first role in auth.roles (admin by default) 403. The person can no longer sign in; open sessions stop within seconds. Their data in the app stays. The last user with the first role cannot be removed (409).",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the user (their sub in x-hypamoon-user).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed; their data stays.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/emails": {
      "get": {
        "operationId": "listEmails",
        "tags": [
          "Email"
        ],
        "summary": "List sent emails",
        "description": "The project's emails, newest first, each with its status, a short reason when it bounced, was marked as spam, is delayed or was not sent, the status per recipient and a timeline. Plus the state of the Mail module: on or off, paused, the daily limit and what was sent in the last 24 hours. Filter on the app that sent them (app_id), a recipient (to), a status and a start time (since).",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Emails per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app_id",
            "in": "query",
            "description": "Only emails this app sent with env.HYPAMOON.sendEmail (an id from list_apps).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only emails with this address in to, cc or bcc.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only emails with this status.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "sent",
                "delivered",
                "bounced",
                "complained",
                "failed",
                "simulated"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Only emails sent at or after this moment, like 2026-10-01 or 2026-10-15T09:00:00Z.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The emails and the state of the module.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "mail": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "Is the Mail module on for this project? The owner turns it on in the portal."
                        },
                        "paused": {
                          "type": "boolean"
                        },
                        "pause_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "daily_limit": {
                          "type": "integer",
                          "description": "Recipients per 24 hours (to, cc and bcc each count)."
                        },
                        "sent_last_24h": {
                          "type": "integer",
                          "description": "Recipients in the last 24 hours."
                        },
                        "new_recipients": {
                          "type": "object",
                          "properties": {
                            "daily_limit": {
                              "type": "integer",
                              "description": "New recipients a day that only appear in documents your app wrote: 25 to start, 100 and then 500 as emails are delivered without bounces or complaints (sooner with a verified business or a paid plan)."
                            },
                            "used_last_24h": {
                              "type": "integer"
                            },
                            "left": {
                              "type": "integer"
                            },
                            "level": {
                              "type": "integer",
                              "description": "1, 2 or 3."
                            },
                            "how_to_raise": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "What raises the limit, in a sentence for the user; null at the top level."
                            }
                          },
                          "required": [
                            "daily_limit",
                            "used_last_24h",
                            "left",
                            "level",
                            "how_to_raise"
                          ],
                          "description": "App users who signed in, buyers with a paid order and addresses the owner confirmed do not count here."
                        }
                      },
                      "required": [
                        "enabled",
                        "paused",
                        "pause_reason",
                        "daily_limit",
                        "sent_last_24h"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor",
                    "mail"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": null,
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [
                        {
                          "filename": "quote.pdf",
                          "content_type": "application/pdf",
                          "size": 48213
                        }
                      ],
                      "reply_to": "office@acmeplumbing.com",
                      "subject": "We received your request",
                      "category": "transactional",
                      "status": "sent",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "sent",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:01.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": null,
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ],
                  "next_cursor": null,
                  "mail": {
                    "enabled": true,
                    "paused": false,
                    "pause_reason": null,
                    "daily_limit": 100,
                    "sent_last_24h": 12
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "sendEmail",
        "tags": [
          "Email"
        ],
        "summary": "Send one email",
        "description": "Sends one email from an address on a verified sending domain of this project, like Resend: to is one address or a list, with optional cc and bcc (at most 50 recipients together). Everyone in to and cc sees the others; use bcc or POST /emails/batch when people must not see each other. Every recipient must appear in the project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner. category other (the default) adds an unsubscribe link and header plus the project's postal address (from PUT /project/company, required by the US CAN-SPAM Act; without it the email is refused with company_details_missing) and goes to exactly one recipient; several recipients need category transactional (a reply to something they all did). attachments: at most 10 files, 10 MB together, each with a filename and one of content (base64), media_id (a file from Media) or path (an https address on one of this project's sites). Allowed: PDF, PNG, JPG, GIF, WebP, and ICS, TXT or CSV as UTF-8 text; the type is checked on the file's first bytes. Keep the request under about 4 MB; use media_id or path for larger files. Every email passes a content check (no phishing, no scripts or forms, no links that hide where they go). Limits count every recipient: a daily limit per project, 200 recipients and 20 calls per minute, and a cap on new recipients a day that only appear in documents your app wrote (25 to start, growing to 100 and 500 as emails are delivered without bounces or complaints; see mail.new_recipients in GET /emails). Each recipient also counts as one email for the bundle. Sending pauses automatically when bounces exceed 5% or spam complaints exceed 0.1% over the last 200 emails. With a test key nothing is sent: after all the same checks, the email gets status simulated. An Idempotency-Key (header or idempotency_key) returns the same email instead of a second one. template_id with data sends a template (POST /email-templates) instead of subject, html and text. send_at sends it later (at most 30 days ahead): the answer is then a scheduled email (202), which POST /scheduled-emails/{id}/cancel stops.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. The same key returns the same email instead of sending it again.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320,
                    "description": "The sender: an address on a verified sending domain of this project, optionally with a name, like \"Acme Plumbing <hello@acme.com>\"."
                  },
                  "to": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 254
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 254
                        },
                        "minItems": 1,
                        "maxItems": 50
                      }
                    ],
                    "description": "One address or a list (at most 50 recipients in to, cc and bcc together). Everyone in to and cc sees the others, as in any email; use bcc, or a batch with one email per person, when they must not. Every address must appear in this project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner."
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 3,
                      "maxLength": 254
                    },
                    "maxItems": 50,
                    "description": "Optional. Copy recipients; they see the to and cc addresses. The same rules as to."
                  },
                  "bcc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 3,
                      "maxLength": 254
                    },
                    "maxItems": 50,
                    "description": "Optional. Hidden recipients; nobody sees them. The same rules as to."
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The subject line. Required without template_id; with template_id it comes from the template."
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "The plain text body. Send text, html or both."
                  },
                  "html": {
                    "type": "string",
                    "maxLength": 200000,
                    "description": "The HTML body. No scripts, no forms; links must show where they go."
                  },
                  "reply_to": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "Optional. Where replies go, like the user's own mailbox."
                  },
                  "attachments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "filename": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 120,
                          "description": "The file name with its extension, like invoice.pdf. Required with content and path; with media_id it defaults to the name in Media. Allowed: pdf, png, jpg, jpeg, gif, webp, ics, txt, csv."
                        },
                        "content": {
                          "type": "string",
                          "description": "The file as base64. Counts toward the request size (keep the whole request under about 4 MB); for a larger file use media_id or path."
                        },
                        "media_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "A file from this project's Media library (its id from list_media)."
                        },
                        "path": {
                          "type": "string",
                          "maxLength": 2048,
                          "description": "The https address of a file on one of this project's sites, like https://acme.hypamoon.app/menu.pdf. Other hosts are refused."
                        },
                        "content_type": {
                          "type": "string",
                          "maxLength": 100,
                          "description": "Optional and only informative: the type is always taken from the file itself."
                        }
                      },
                      "additionalProperties": false,
                      "description": "One attachment: filename plus exactly one of content, media_id or path. The type is checked on the file's first bytes."
                    },
                    "description": "Optional. At most 10 files, 10 MB together: PDF, PNG, JPG, GIF, WebP, and ICS, TXT or CSV as UTF-8 text. Never executables, HTML, SVG or archives."
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "other"
                    ],
                    "default": "other",
                    "description": "transactional: a reply to something the recipients did (an order, a booking, a form they sent). other (default): anything else; it gets an unsubscribe link with the project's postal address (needs a business address in the company details) and a List-Unsubscribe header, is not sent to people who unsubscribed, and goes to exactly one recipient (use a batch for more people)."
                  },
                  "in_reply_to": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The id of the form submission (a document from a public form) or the chat conversation this email answers. Required when the recipient appears in the project only through a form or a chat; the address must be in that submission or conversation."
                  },
                  "check_recipients": {
                    "type": "boolean",
                    "description": "Optional, default true: before the first email to an address, check that its domain exists and accepts email, and refuse likely typos (like gmial.com) and, for category other, throwaway addresses. false skips those checks; an address that bounced before is always refused."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "pattern": "^[\\x21-\\x7E]{1,255}$",
                    "description": "Optional. The same key returns the same email instead of sending a second one (also as the Idempotency-Key header)."
                  },
                  "template_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional. A template (create_email_template): the subject, html and text come from it, filled with data. Leave out subject, html and text."
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 2000
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "description": "With template_id: the values for its variables, like { \"name\": \"Sam\" }."
                  },
                  "send_at": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Optional. Send later, like 2026-11-02T09:00:00Z, at most 30 days ahead: the answer is a scheduled email (202) that cancel_scheduled_email can stop. All checks except the form run when it is sent."
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "additionalProperties": false
              },
              "example": {
                "from": "Acme Plumbing <hello@acmeplumbing.com>",
                "to": "sam@example.org",
                "subject": "We received your request",
                "text": "Hi Sam, thanks for your request. We will call you back today.",
                "reply_to": "office@acmeplumbing.com",
                "category": "transactional",
                "attachments": [
                  {
                    "filename": "quote.pdf",
                    "content": "JVBERi0xLjcKJcfsj6IK..."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same Idempotency-Key as before: the original email, and nothing is sent again.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": null,
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [
                    {
                      "filename": "quote.pdf",
                      "content_type": "application/pdf",
                      "size": 48213
                    }
                  ],
                  "reply_to": "office@acmeplumbing.com",
                  "subject": "We received your request",
                  "category": "transactional",
                  "status": "sent",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "sent",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:01.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": null,
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "201": {
            "description": "Accepted for delivery (or simulated with a test key).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": null,
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [
                    {
                      "filename": "quote.pdf",
                      "content_type": "application/pdf",
                      "size": 48213
                    }
                  ],
                  "reply_to": "office@acmeplumbing.com",
                  "subject": "We received your request",
                  "category": "transactional",
                  "status": "sent",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "sent",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:01.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": null,
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "202": {
            "description": "With send_at: the scheduled email. get_scheduled_email shows it; once sent, email_id is the email.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the scheduled email. cancel_scheduled_email takes it."
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "api",
                        "automation",
                        "sequence",
                        "list",
                        "confirmation"
                      ],
                      "description": "api: send_email with send_at. automation, sequence, list: sent by those. confirmation: the confirmation email of a list."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "scheduled",
                        "sending",
                        "sent",
                        "canceled",
                        "skipped",
                        "failed"
                      ],
                      "description": "scheduled (waits for send_at), sending, sent (email_id is the email), canceled, skipped (the person unsubscribed or left the list or sequence), failed (reason says why)."
                    },
                    "send_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "template_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "email_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The email once it was sent; get_email shows whether it was delivered."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "source",
                    "status",
                    "send_at",
                    "to",
                    "subject",
                    "template_id",
                    "email_id",
                    "reason",
                    "test",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
                  "source": "api",
                  "status": "scheduled",
                  "send_at": "2026-10-16T08:00:00.000Z",
                  "to": "sam@example.org",
                  "subject": "Your appointment tomorrow",
                  "template_id": null,
                  "email_id": null,
                  "reason": null,
                  "test": false,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, mail_not_enabled, email_paused, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "mail_not_enabled",
              "email_paused",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/mail_not_enabled",
                            "https://hypamoon.com/docs/errors/email_paused",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress, email_domain_not_verified.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "email_domain_not_verified"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/email_domain_not_verified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "Error: attachment_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "attachment_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/attachment_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, email_address_invalid, company_details_missing, email_blocked, attachment_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "email_address_invalid",
              "company_details_missing",
              "email_blocked",
              "attachment_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/email_address_invalid",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/email_blocked",
                            "https://hypamoon.com/docs/errors/attachment_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, email_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "email_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/email_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-emails": {
      "post": {
        "operationId": "sendEmailFromApp",
        "tags": [
          "Email"
        ],
        "summary": "Send one email (from an app)",
        "description": "For an app's own server code only: await env.HYPAMOON.sendEmail({ to, subject, text }) does this call, POST env.HYPAMOON_API + \"/app-emails\" without an Authorization header; Hypamoon adds the app's key, and any other key gets 403 (other keys use POST /emails with messages:write). The same rules as POST /emails, for the app's project: the Mail module on, recipients (to, cc, bcc) that the project knows (a user of the app, a form submission, a document, a chat conversation) or that the owner confirmed, attachments, the daily and per-minute limits per recipient, and the content check. In the app, an attachment's content may be a Uint8Array (like the result of env.HYPAMOON.renderPdf); the wrapper sends it as base64. from is optional: without it the email comes from noreply@ on the project's verified sending domain (the oldest one). No signed-in user is needed, so a scheduled job can send too. With a test key nothing is sent (status simulated).",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. The same key returns the same email instead of sending it again.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320,
                    "description": "Optional. An address on a verified sending domain of this project, optionally with a name. Without it: noreply@ on the project's oldest verified sending domain."
                  },
                  "to": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 254
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 254
                        },
                        "minItems": 1,
                        "maxItems": 50
                      }
                    ],
                    "description": "One address or a list (at most 50 recipients in to, cc and bcc together). Everyone in to and cc sees the others, as in any email; use bcc, or a batch with one email per person, when they must not. Every address must appear in this project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner."
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 3,
                      "maxLength": 254
                    },
                    "maxItems": 50,
                    "description": "Optional. Copy recipients; they see the to and cc addresses. The same rules as to."
                  },
                  "bcc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 3,
                      "maxLength": 254
                    },
                    "maxItems": 50,
                    "description": "Optional. Hidden recipients; nobody sees them. The same rules as to."
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The subject line."
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "The plain text body. Send text, html or both."
                  },
                  "html": {
                    "type": "string",
                    "maxLength": 200000,
                    "description": "The HTML body. No scripts, no forms; links must show where they go."
                  },
                  "reply_to": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "Optional. Where replies go, like the user's own mailbox."
                  },
                  "attachments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "filename": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 120,
                          "description": "The file name with its extension, like invoice.pdf. Required with content and path; with media_id it defaults to the name in Media. Allowed: pdf, png, jpg, jpeg, gif, webp, ics, txt, csv."
                        },
                        "content": {
                          "type": "string",
                          "description": "The file as base64. Counts toward the request size (keep the whole request under about 4 MB); for a larger file use media_id or path."
                        },
                        "media_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "A file from this project's Media library (its id from list_media)."
                        },
                        "path": {
                          "type": "string",
                          "maxLength": 2048,
                          "description": "The https address of a file on one of this project's sites, like https://acme.hypamoon.app/menu.pdf. Other hosts are refused."
                        },
                        "content_type": {
                          "type": "string",
                          "maxLength": 100,
                          "description": "Optional and only informative: the type is always taken from the file itself."
                        }
                      },
                      "additionalProperties": false,
                      "description": "One attachment: filename plus exactly one of content, media_id or path. The type is checked on the file's first bytes."
                    },
                    "description": "Optional. At most 10 files, 10 MB together: PDF, PNG, JPG, GIF, WebP, and ICS, TXT or CSV as UTF-8 text. Never executables, HTML, SVG or archives."
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "other"
                    ],
                    "default": "other",
                    "description": "transactional: a reply to something the recipients did (an order, a booking, a form they sent). other (default): anything else; it gets an unsubscribe link with the project's postal address (needs a business address in the company details) and a List-Unsubscribe header, is not sent to people who unsubscribed, and goes to exactly one recipient (use a batch for more people)."
                  },
                  "in_reply_to": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The id of the form submission (a document from a public form) or the chat conversation this email answers. Required when the recipient appears in the project only through a form or a chat; the address must be in that submission or conversation."
                  },
                  "check_recipients": {
                    "type": "boolean",
                    "description": "Optional, default true: before the first email to an address, check that its domain exists and accepts email, and refuse likely typos (like gmial.com) and, for category other, throwaway addresses. false skips those checks; an address that bounced before is always refused."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "pattern": "^[\\x21-\\x7E]{1,255}$",
                    "description": "Optional. The same key returns the same email instead of sending a second one (also as the Idempotency-Key header)."
                  }
                },
                "required": [
                  "to",
                  "subject"
                ],
                "additionalProperties": false
              },
              "example": {
                "to": "sam@example.org",
                "subject": "Your hours for week 41 are approved",
                "text": "Hi Sam, your hours for week 41 were approved.",
                "category": "transactional"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same Idempotency-Key as before: the original email, and nothing is sent again.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": null,
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [
                    {
                      "filename": "quote.pdf",
                      "content_type": "application/pdf",
                      "size": 48213
                    }
                  ],
                  "reply_to": "office@acmeplumbing.com",
                  "subject": "We received your request",
                  "category": "transactional",
                  "status": "sent",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "sent",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:01.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": null,
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "201": {
            "description": "Accepted for delivery (or simulated with a test key).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": null,
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [
                    {
                      "filename": "quote.pdf",
                      "content_type": "application/pdf",
                      "size": 48213
                    }
                  ],
                  "reply_to": "office@acmeplumbing.com",
                  "subject": "We received your request",
                  "category": "transactional",
                  "status": "sent",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "sent",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:01.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": null,
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, mail_not_enabled, email_paused, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "mail_not_enabled",
              "email_paused",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/mail_not_enabled",
                            "https://hypamoon.com/docs/errors/email_paused",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress, email_domain_not_verified.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "email_domain_not_verified"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/email_domain_not_verified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "Error: attachment_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "attachment_too_large"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/attachment_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, email_address_invalid, company_details_missing, email_blocked, attachment_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "email_address_invalid",
              "company_details_missing",
              "email_blocked",
              "attachment_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/email_address_invalid",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/email_blocked",
                            "https://hypamoon.com/docs/errors/attachment_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, email_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "email_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/email_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "get": {
        "operationId": "listEmailsFromApp",
        "tags": [
          "Email"
        ],
        "summary": "List the emails of an app (from the app)",
        "description": "For an app's own server code only: await env.HYPAMOON.listEmails({ to, status, since, limit, cursor }) does this call without an Authorization header; Hypamoon adds the app's key, and any other key gets 403. Only the emails this app sent with sendEmail or sendEmailBatch, newest first, never those of another app or of the API. Each with its status, reason, status per recipient and timeline, as GET /emails/{id}.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Emails per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only emails with this address in to, cc or bcc.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only emails with this status.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "sent",
                "delivered",
                "bounced",
                "complained",
                "failed",
                "simulated"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Only emails sent at or after this moment, like 2026-10-01 or 2026-10-15T09:00:00Z.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The app's emails.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                      "from": "noreply@acmeplumbing.com",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [],
                      "reply_to": null,
                      "subject": "Your hours for week 41 are approved",
                      "category": "transactional",
                      "status": "delivered",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "delivered",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:04.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "delivered",
                          "at": "2026-10-06T10:15:04.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": "2026-10-06T10:15:04.000Z",
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/emails/batch": {
      "post": {
        "operationId": "sendEmailBatch",
        "tags": [
          "Email"
        ],
        "summary": "Send a batch of emails",
        "description": "Sends 1 to 100 separate emails in one call, like Resend's batch: each email has its own to (and its own text), so everyone sees only their own email, for example a personal invitation per person. Each email follows the rules of POST /emails, except that a batch has no attachments (send those one by one). All or nothing: every email is checked first, and if one is refused, none is sent and the error says which (index). Every email and every recipient counts toward the limits and the bundle; the whole batch is one call for the per-minute limit. An Idempotency-Key (header or idempotency_key) returns the same emails instead of sending them again. With a test key nothing is sent (status simulated).",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. The same key returns the same emails instead of sending them again.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 320,
                          "description": "The sender: an address on a verified sending domain of this project, optionally with a name, like \"Acme Plumbing <hello@acme.com>\"."
                        },
                        "to": {
                          "anyOf": [
                            {
                              "type": "string",
                              "minLength": 3,
                              "maxLength": 254
                            },
                            {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 3,
                                "maxLength": 254
                              },
                              "minItems": 1,
                              "maxItems": 50
                            }
                          ],
                          "description": "One address or a list (at most 50 recipients in to, cc and bcc together). Everyone in to and cc sees the others, as in any email; use bcc, or a batch with one email per person, when they must not. Every address must appear in this project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner."
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 254
                          },
                          "maxItems": 50,
                          "description": "Optional. Copy recipients; they see the to and cc addresses. The same rules as to."
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 254
                          },
                          "maxItems": 50,
                          "description": "Optional. Hidden recipients; nobody sees them. The same rules as to."
                        },
                        "subject": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "The subject line."
                        },
                        "text": {
                          "type": "string",
                          "maxLength": 100000,
                          "description": "The plain text body. Send text, html or both."
                        },
                        "html": {
                          "type": "string",
                          "maxLength": 200000,
                          "description": "The HTML body. No scripts, no forms; links must show where they go."
                        },
                        "reply_to": {
                          "type": "string",
                          "maxLength": 254,
                          "description": "Optional. Where replies go, like the user's own mailbox."
                        },
                        "category": {
                          "type": "string",
                          "enum": [
                            "transactional",
                            "other"
                          ],
                          "default": "other",
                          "description": "transactional: a reply to something the recipients did (an order, a booking, a form they sent). other (default): anything else; it gets an unsubscribe link with the project's postal address (needs a business address in the company details) and a List-Unsubscribe header, is not sent to people who unsubscribed, and goes to exactly one recipient (use a batch for more people)."
                        },
                        "in_reply_to": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The id of the form submission (a document from a public form) or the chat conversation this email answers. Required when the recipient appears in the project only through a form or a chat; the address must be in that submission or conversation."
                        },
                        "check_recipients": {
                          "type": "boolean",
                          "description": "Optional, default true: before the first email to an address, check that its domain exists and accepts email, and refuse likely typos (like gmial.com) and, for category other, throwaway addresses. false skips those checks; an address that bounced before is always refused."
                        }
                      },
                      "required": [
                        "from",
                        "to",
                        "subject"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 100,
                    "description": "1 to 100 separate emails, each with its own to (and its own text, for personal invitations): everyone sees only their own email. No attachments in a batch; send those one by one."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "pattern": "^[\\x21-\\x7E]{1,255}$",
                    "description": "Optional. The same key returns the same emails instead of sending them again (also as the Idempotency-Key header)."
                  }
                },
                "required": [
                  "emails"
                ],
                "additionalProperties": false
              },
              "example": {
                "emails": [
                  {
                    "from": "Acme Plumbing <hello@acmeplumbing.com>",
                    "to": "sam@example.org",
                    "subject": "Your invitation",
                    "text": "Hi Sam, you are invited to our open day on 1 November.",
                    "category": "transactional"
                  },
                  {
                    "from": "Acme Plumbing <hello@acmeplumbing.com>",
                    "to": "kim@example.org",
                    "subject": "Your invitation",
                    "text": "Hi Kim, you are invited to our open day on 1 November.",
                    "category": "transactional"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same Idempotency-Key as before: the original emails, and nothing is sent again.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      },
                      "description": "The emails in the order of the request."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": null,
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [
                        {
                          "filename": "quote.pdf",
                          "content_type": "application/pdf",
                          "size": 48213
                        }
                      ],
                      "reply_to": "office@acmeplumbing.com",
                      "subject": "We received your request",
                      "category": "transactional",
                      "status": "sent",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "sent",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:01.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": null,
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Accepted for delivery (or simulated with a test key), in the order of the request.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      },
                      "description": "The emails in the order of the request."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": null,
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [
                        {
                          "filename": "quote.pdf",
                          "content_type": "application/pdf",
                          "size": 48213
                        }
                      ],
                      "reply_to": "office@acmeplumbing.com",
                      "subject": "We received your request",
                      "category": "transactional",
                      "status": "sent",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "sent",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:01.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": null,
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Payment Required: insufficient_balance.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "insufficient_balance"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/insufficient_balance"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, mail_not_enabled, email_paused, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "mail_not_enabled",
              "email_paused",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/mail_not_enabled",
                            "https://hypamoon.com/docs/errors/email_paused",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, idempotency_request_in_progress, email_domain_not_verified.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "idempotency_request_in_progress",
              "email_domain_not_verified"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/idempotency_request_in_progress",
                            "https://hypamoon.com/docs/errors/email_domain_not_verified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, email_address_invalid, company_details_missing, email_blocked, attachment_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "email_address_invalid",
              "company_details_missing",
              "email_blocked",
              "attachment_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/email_address_invalid",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/email_blocked",
                            "https://hypamoon.com/docs/errors/attachment_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, email_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "email_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/email_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-emails/batch": {
      "post": {
        "operationId": "sendEmailBatchFromApp",
        "tags": [
          "Email"
        ],
        "summary": "Send a batch of emails (from an app)",
        "description": "For an app's own server code only: await env.HYPAMOON.sendEmailBatch([{ to, subject, text }, ...]) does this call without an Authorization header; Hypamoon adds the app's key, and any other key gets 403. The rules of POST /emails/batch for the app's project; from is optional per email, as in sendEmail.",
        "x-hypamoon-scope": "data:write",
        "security": [
          {
            "ApiKey": [
              "data:write"
            ]
          },
          {
            "OAuth2": [
              "data:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional. The same key returns the same emails instead of sending them again.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "minLength": 3,
                          "maxLength": 320,
                          "description": "Optional, as in sendEmail."
                        },
                        "to": {
                          "anyOf": [
                            {
                              "type": "string",
                              "minLength": 3,
                              "maxLength": 254
                            },
                            {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 3,
                                "maxLength": 254
                              },
                              "minItems": 1,
                              "maxItems": 50
                            }
                          ],
                          "description": "One address or a list (at most 50 recipients in to, cc and bcc together). Everyone in to and cc sees the others, as in any email; use bcc, or a batch with one email per person, when they must not. Every address must appear in this project (a form submission, a document, a chat conversation, a user of one of your apps) or be confirmed by the owner."
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 254
                          },
                          "maxItems": 50,
                          "description": "Optional. Copy recipients; they see the to and cc addresses. The same rules as to."
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 254
                          },
                          "maxItems": 50,
                          "description": "Optional. Hidden recipients; nobody sees them. The same rules as to."
                        },
                        "subject": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200,
                          "description": "The subject line."
                        },
                        "text": {
                          "type": "string",
                          "maxLength": 100000,
                          "description": "The plain text body. Send text, html or both."
                        },
                        "html": {
                          "type": "string",
                          "maxLength": 200000,
                          "description": "The HTML body. No scripts, no forms; links must show where they go."
                        },
                        "reply_to": {
                          "type": "string",
                          "maxLength": 254,
                          "description": "Optional. Where replies go, like the user's own mailbox."
                        },
                        "category": {
                          "type": "string",
                          "enum": [
                            "transactional",
                            "other"
                          ],
                          "default": "other",
                          "description": "transactional: a reply to something the recipients did (an order, a booking, a form they sent). other (default): anything else; it gets an unsubscribe link with the project's postal address (needs a business address in the company details) and a List-Unsubscribe header, is not sent to people who unsubscribed, and goes to exactly one recipient (use a batch for more people)."
                        },
                        "in_reply_to": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The id of the form submission (a document from a public form) or the chat conversation this email answers. Required when the recipient appears in the project only through a form or a chat; the address must be in that submission or conversation."
                        },
                        "check_recipients": {
                          "type": "boolean",
                          "description": "Optional, default true: before the first email to an address, check that its domain exists and accepts email, and refuse likely typos (like gmial.com) and, for category other, throwaway addresses. false skips those checks; an address that bounced before is always refused."
                        }
                      },
                      "required": [
                        "to",
                        "subject"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "idempotency_key": {
                    "type": "string",
                    "maxLength": 255
                  }
                },
                "required": [
                  "emails"
                ],
                "additionalProperties": false
              },
              "example": {
                "emails": [
                  {
                    "to": "sam@example.org",
                    "subject": "Your invitation",
                    "text": "Hi Sam, see you on 1 November.",
                    "category": "transactional"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same Idempotency-Key as before: the original emails.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      },
                      "description": "The emails in the order of the request."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": null,
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [
                        {
                          "filename": "quote.pdf",
                          "content_type": "application/pdf",
                          "size": 48213
                        }
                      ],
                      "reply_to": "office@acmeplumbing.com",
                      "subject": "We received your request",
                      "category": "transactional",
                      "status": "sent",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "sent",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:01.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": null,
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Accepted for delivery (or simulated with a test key).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                          },
                          "app_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                          },
                          "from": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The first address in to. All recipients are in recipients."
                          },
                          "recipients": {
                            "type": "object",
                            "properties": {
                              "to": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "cc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "bcc": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "to",
                              "cc",
                              "bcc"
                            ],
                            "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                          },
                          "recipient_count": {
                            "type": "integer",
                            "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                          },
                          "attachments": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "filename": {
                                  "type": "string"
                                },
                                "content_type": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "In bytes."
                                }
                              },
                              "required": [
                                "filename",
                                "content_type",
                                "size"
                              ]
                            },
                            "description": "Name, type and size of each attachment. The content is never stored."
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                          },
                          "recipient_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "confirmed",
                              "member",
                              "form",
                              "document",
                              "conversation",
                              "app_user",
                              "buyer",
                              "pass_holder",
                              null
                            ],
                            "description": "Where the recipient appears in the project."
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                          },
                          "recipient_status": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "email": {
                                  "type": "string",
                                  "description": "The address. Masked after 90 days, like s***@example.org."
                                },
                                "field": {
                                  "type": "string",
                                  "enum": [
                                    "to",
                                    "cc",
                                    "bcc"
                                  ]
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ]
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When this recipient's status last changed."
                                }
                              },
                              "required": [
                                "email",
                                "field",
                                "status",
                                "reason",
                                "updated_at"
                              ]
                            },
                            "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                          },
                          "timeline": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "queued",
                                    "sent",
                                    "delivered",
                                    "delayed",
                                    "bounced",
                                    "complained",
                                    "failed",
                                    "simulated"
                                  ],
                                  "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                                },
                                "at": {
                                  "type": "string",
                                  "description": "When it happened (ISO 8601)."
                                },
                                "recipient": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The recipient this step is about; null for every recipient."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                                }
                              },
                              "required": [
                                "status",
                                "at",
                                "recipient",
                                "reason"
                              ]
                            },
                            "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivered_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "bounced_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "complained_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "app_id",
                          "from",
                          "to",
                          "recipients",
                          "recipient_count",
                          "attachments",
                          "reply_to",
                          "subject",
                          "category",
                          "status",
                          "recipient_source",
                          "test",
                          "error",
                          "reason",
                          "recipient_status",
                          "timeline",
                          "created_at",
                          "sent_at",
                          "delivered_at",
                          "bounced_at",
                          "complained_at"
                        ]
                      },
                      "description": "The emails in the order of the request."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "app_id": null,
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "to": "sam@example.org",
                      "recipients": {
                        "to": [
                          "sam@example.org"
                        ],
                        "cc": [],
                        "bcc": []
                      },
                      "recipient_count": 1,
                      "attachments": [
                        {
                          "filename": "quote.pdf",
                          "content_type": "application/pdf",
                          "size": 48213
                        }
                      ],
                      "reply_to": "office@acmeplumbing.com",
                      "subject": "We received your request",
                      "category": "transactional",
                      "status": "sent",
                      "recipient_source": "form",
                      "test": false,
                      "error": null,
                      "reason": null,
                      "recipient_status": [
                        {
                          "email": "sam@example.org",
                          "field": "to",
                          "status": "sent",
                          "reason": null,
                          "updated_at": "2026-10-06T10:15:01.000Z"
                        }
                      ],
                      "timeline": [
                        {
                          "status": "queued",
                          "at": "2026-10-06T10:15:00.000Z",
                          "recipient": null,
                          "reason": null
                        },
                        {
                          "status": "sent",
                          "at": "2026-10-06T10:15:01.000Z",
                          "recipient": null,
                          "reason": null
                        }
                      ],
                      "created_at": "2026-10-06T10:15:00.000Z",
                      "sent_at": "2026-10-06T10:15:01.000Z",
                      "delivered_at": null,
                      "bounced_at": null,
                      "complained_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json, idempotency_key_invalid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json",
              "idempotency_key_invalid"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json",
                            "https://hypamoon.com/docs/errors/idempotency_key_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden, mail_not_enabled, email_paused, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden",
              "mail_not_enabled",
              "email_paused",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden",
                            "https://hypamoon.com/docs/errors/mail_not_enabled",
                            "https://hypamoon.com/docs/errors/email_paused",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: idempotency_key_reused, email_domain_not_verified.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "idempotency_key_reused",
              "email_domain_not_verified"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/idempotency_key_reused",
                            "https://hypamoon.com/docs/errors/email_domain_not_verified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed, email_address_invalid, company_details_missing, email_blocked, attachment_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed",
              "email_address_invalid",
              "company_details_missing",
              "email_blocked",
              "attachment_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed",
                            "https://hypamoon.com/docs/errors/email_address_invalid",
                            "https://hypamoon.com/docs/errors/company_details_missing",
                            "https://hypamoon.com/docs/errors/email_blocked",
                            "https://hypamoon.com/docs/errors/attachment_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, email_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "email_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/email_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/emails/{id}": {
      "get": {
        "operationId": "getEmail",
        "tags": [
          "Email"
        ],
        "summary": "Get one email",
        "description": "One email with its status (queued, sent, delivered, bounced, complained: marked as spam, failed or simulated), a short reason, the status per recipient (one can bounce while the others are delivered) and the timeline with times.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the email.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The email.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": null,
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [
                    {
                      "filename": "quote.pdf",
                      "content_type": "application/pdf",
                      "size": 48213
                    }
                  ],
                  "reply_to": "office@acmeplumbing.com",
                  "subject": "We received your request",
                  "category": "transactional",
                  "status": "delivered",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "delivered",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:04.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "delivered",
                      "at": "2026-10-06T10:15:04.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": "2026-10-06T10:15:04.000Z",
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/app-emails/{id}": {
      "get": {
        "operationId": "getEmailFromApp",
        "tags": [
          "Email"
        ],
        "summary": "Get one email of an app (from the app)",
        "description": "For an app's own server code only: await env.HYPAMOON.getEmail(id), with the id that sendEmail or sendEmailBatch returned. The same fields as GET /emails/{id}. An email of another app or of the API is not found.",
        "x-hypamoon-scope": "data:read",
        "security": [
          {
            "ApiKey": [
              "data:read"
            ]
          },
          {
            "OAuth2": [
              "data:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the email.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The email.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the email. sendEmail and sendEmailBatch in an app return it; getEmail(id) reads it back."
                    },
                    "app_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The app that sent it with env.HYPAMOON.sendEmail; null when it was sent with the API, by an agent or from the portal."
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string",
                      "description": "The first address in to. All recipients are in recipients."
                    },
                    "recipients": {
                      "type": "object",
                      "properties": {
                        "to": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "cc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "bcc": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "to",
                        "cc",
                        "bcc"
                      ],
                      "description": "Everyone this email went to. After 90 days the addresses are masked, like s***@example.org."
                    },
                    "recipient_count": {
                      "type": "integer",
                      "description": "to, cc and bcc together; each counts toward the limits and the bundle."
                    },
                    "attachments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": {
                            "type": "string"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer",
                            "description": "In bytes."
                          }
                        },
                        "required": [
                          "filename",
                          "content_type",
                          "size"
                        ]
                      },
                      "description": "Name, type and size of each attachment. The content is never stored."
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "sent",
                        "delivered",
                        "bounced",
                        "complained",
                        "failed",
                        "simulated"
                      ],
                      "description": "queued, sent (accepted for delivery), delivered, bounced, complained (marked as spam), failed, or simulated (test mode)."
                    },
                    "recipient_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "confirmed",
                        "member",
                        "form",
                        "document",
                        "conversation",
                        "app_user",
                        "buyer",
                        "pass_holder",
                        null
                      ],
                      "description": "Where the recipient appears in the project."
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why the email bounced, was marked as spam, is delayed or was not sent, in plain words. null otherwise."
                    },
                    "recipient_status": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "description": "The address. Masked after 90 days, like s***@example.org."
                          },
                          "field": {
                            "type": "string",
                            "enum": [
                              "to",
                              "cc",
                              "bcc"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ]
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it bounced, was marked as spam, is delayed or was not sent."
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When this recipient's status last changed."
                          }
                        },
                        "required": [
                          "email",
                          "field",
                          "status",
                          "reason",
                          "updated_at"
                        ]
                      },
                      "description": "The status per recipient. With several recipients one can bounce while the others are delivered."
                    },
                    "timeline": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "delivered",
                              "delayed",
                              "bounced",
                              "complained",
                              "failed",
                              "simulated"
                            ],
                            "description": "queued, sent, delivered, delayed (the recipient's server has not accepted it yet; retried automatically), bounced, complained, failed or simulated."
                          },
                          "at": {
                            "type": "string",
                            "description": "When it happened (ISO 8601)."
                          },
                          "recipient": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The recipient this step is about; null for every recipient."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "A short reason in plain words, for delayed, bounced, complained and failed."
                          }
                        },
                        "required": [
                          "status",
                          "at",
                          "recipient",
                          "reason"
                        ]
                      },
                      "description": "Every step, oldest first: queued, then sent (or simulated, or failed), then what the recipient's mail server reported."
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "sent_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delivered_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "bounced_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "complained_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "app_id",
                    "from",
                    "to",
                    "recipients",
                    "recipient_count",
                    "attachments",
                    "reply_to",
                    "subject",
                    "category",
                    "status",
                    "recipient_source",
                    "test",
                    "error",
                    "reason",
                    "recipient_status",
                    "timeline",
                    "created_at",
                    "sent_at",
                    "delivered_at",
                    "bounced_at",
                    "complained_at"
                  ]
                },
                "example": {
                  "id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "from": "noreply@acmeplumbing.com",
                  "to": "sam@example.org",
                  "recipients": {
                    "to": [
                      "sam@example.org"
                    ],
                    "cc": [],
                    "bcc": []
                  },
                  "recipient_count": 1,
                  "attachments": [],
                  "reply_to": null,
                  "subject": "Your hours for week 41 are approved",
                  "category": "transactional",
                  "status": "delivered",
                  "recipient_source": "form",
                  "test": false,
                  "error": null,
                  "reason": null,
                  "recipient_status": [
                    {
                      "email": "sam@example.org",
                      "field": "to",
                      "status": "delivered",
                      "reason": null,
                      "updated_at": "2026-10-06T10:15:04.000Z"
                    }
                  ],
                  "timeline": [
                    {
                      "status": "queued",
                      "at": "2026-10-06T10:15:00.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "sent",
                      "at": "2026-10-06T10:15:01.000Z",
                      "recipient": null,
                      "reason": null
                    },
                    {
                      "status": "delivered",
                      "at": "2026-10-06T10:15:04.000Z",
                      "recipient": null,
                      "reason": null
                    }
                  ],
                  "created_at": "2026-10-06T10:15:00.000Z",
                  "sent_at": "2026-10-06T10:15:01.000Z",
                  "delivered_at": "2026-10-06T10:15:04.000Z",
                  "bounced_at": null,
                  "complained_at": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-domains": {
      "get": {
        "operationId": "listEmailDomains",
        "tags": [
          "Email"
        ],
        "summary": "List sending domains",
        "description": "The project's sending domains with their status and DNS records.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The sending domains.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "domain": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "verified",
                              "failed"
                            ]
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "dns": {
                            "type": "string",
                            "enum": [
                              "managed",
                              "manual"
                            ],
                            "description": "managed: the domain's DNS is here and we set the records; manual: the user adds them at their own DNS provider."
                          },
                          "records": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string"
                                },
                                "name": {
                                  "type": "string"
                                },
                                "value": {
                                  "type": "string"
                                },
                                "priority": {
                                  "type": "integer"
                                },
                                "state": {
                                  "type": "string",
                                  "enum": [
                                    "set",
                                    "merged",
                                    "exists",
                                    "conflict",
                                    "add_yourself"
                                  ],
                                  "description": "set: we added it; merged: added to the existing SPF record; exists: already right; conflict: not added, see note; add_yourself: add it at your DNS provider."
                                },
                                "note": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "type",
                                "name",
                                "value",
                                "state"
                              ]
                            }
                          },
                          "dmarc_suggestion": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "type": {
                                "type": "string",
                                "const": "TXT"
                              },
                              "name": {
                                "type": "string"
                              },
                              "value": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "type",
                              "name",
                              "value"
                            ],
                            "description": "Only when the domain has no DMARC record yet. A suggestion; we never set it ourselves."
                          },
                          "next_step": {
                            "type": "string"
                          },
                          "checked_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "verified_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "domain",
                          "status",
                          "test",
                          "dns",
                          "records",
                          "dmarc_suggestion",
                          "next_step",
                          "checked_at",
                          "verified_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                      "domain": "acmeplumbing.com",
                      "status": "pending",
                      "test": false,
                      "dns": "managed",
                      "records": [
                        {
                          "type": "MX",
                          "name": "send.acmeplumbing.com",
                          "value": "feedback-smtp.example.net",
                          "priority": 10,
                          "state": "set"
                        },
                        {
                          "type": "TXT",
                          "name": "send.acmeplumbing.com",
                          "value": "v=spf1 include:spf.example.net ~all",
                          "state": "set"
                        },
                        {
                          "type": "TXT",
                          "name": "mail._domainkey.acmeplumbing.com",
                          "value": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...",
                          "state": "set"
                        }
                      ],
                      "dmarc_suggestion": {
                        "type": "TXT",
                        "name": "_dmarc.acmeplumbing.com",
                        "value": "v=DMARC1; p=none;"
                      },
                      "next_step": "The records are set. Call verify_email_domain in a few minutes; verification usually takes minutes, sometimes up to a few hours.",
                      "checked_at": null,
                      "verified_at": null,
                      "created_at": "2026-10-06T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "addEmailDomain",
        "tags": [
          "Email"
        ],
        "summary": "Add a sending domain",
        "description": "Makes one of this project's domains (connected or purchased) a sending domain. When its DNS is hosted here, we add the records ourselves: an existing SPF record is merged (never a second one), an MX record of the user's mailbox is never touched, and nothing is deleted, so the user's mail keeps working. When the user manages DNS elsewhere, records lists what they need to add there (state add_yourself). A DMARC record is only suggested when there is none. Then call verify_email_domain. Adding the same domain again returns the existing one.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 253,
                    "description": "A domain of this project (connected with connect_domain or bought with register_domain), like example.com."
                  }
                },
                "required": [
                  "domain"
                ],
                "additionalProperties": false
              },
              "example": {
                "domain": "acmeplumbing.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already a sending domain of this project.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "verified",
                        "failed"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "dns": {
                      "type": "string",
                      "enum": [
                        "managed",
                        "manual"
                      ],
                      "description": "managed: the domain's DNS is here and we set the records; manual: the user adds them at their own DNS provider."
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          },
                          "priority": {
                            "type": "integer"
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "set",
                              "merged",
                              "exists",
                              "conflict",
                              "add_yourself"
                            ],
                            "description": "set: we added it; merged: added to the existing SPF record; exists: already right; conflict: not added, see note; add_yourself: add it at your DNS provider."
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value",
                          "state"
                        ]
                      }
                    },
                    "dmarc_suggestion": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "TXT"
                        },
                        "name": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "name",
                        "value"
                      ],
                      "description": "Only when the domain has no DMARC record yet. A suggestion; we never set it ourselves."
                    },
                    "next_step": {
                      "type": "string"
                    },
                    "checked_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "domain",
                    "status",
                    "test",
                    "dns",
                    "records",
                    "dmarc_suggestion",
                    "next_step",
                    "checked_at",
                    "verified_at",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "status": "pending",
                  "test": false,
                  "dns": "managed",
                  "records": [
                    {
                      "type": "MX",
                      "name": "send.acmeplumbing.com",
                      "value": "feedback-smtp.example.net",
                      "priority": 10,
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "send.acmeplumbing.com",
                      "value": "v=spf1 include:spf.example.net ~all",
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "mail._domainkey.acmeplumbing.com",
                      "value": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...",
                      "state": "set"
                    }
                  ],
                  "dmarc_suggestion": {
                    "type": "TXT",
                    "name": "_dmarc.acmeplumbing.com",
                    "value": "v=DMARC1; p=none;"
                  },
                  "next_step": "The records are set. Call verify_email_domain in a few minutes; verification usually takes minutes, sometimes up to a few hours.",
                  "checked_at": null,
                  "verified_at": null,
                  "created_at": "2026-10-06T10:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "The sending domain, with its records.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "verified",
                        "failed"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "dns": {
                      "type": "string",
                      "enum": [
                        "managed",
                        "manual"
                      ],
                      "description": "managed: the domain's DNS is here and we set the records; manual: the user adds them at their own DNS provider."
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          },
                          "priority": {
                            "type": "integer"
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "set",
                              "merged",
                              "exists",
                              "conflict",
                              "add_yourself"
                            ],
                            "description": "set: we added it; merged: added to the existing SPF record; exists: already right; conflict: not added, see note; add_yourself: add it at your DNS provider."
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value",
                          "state"
                        ]
                      }
                    },
                    "dmarc_suggestion": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "TXT"
                        },
                        "name": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "name",
                        "value"
                      ],
                      "description": "Only when the domain has no DMARC record yet. A suggestion; we never set it ourselves."
                    },
                    "next_step": {
                      "type": "string"
                    },
                    "checked_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "domain",
                    "status",
                    "test",
                    "dns",
                    "records",
                    "dmarc_suggestion",
                    "next_step",
                    "checked_at",
                    "verified_at",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "status": "pending",
                  "test": false,
                  "dns": "managed",
                  "records": [
                    {
                      "type": "MX",
                      "name": "send.acmeplumbing.com",
                      "value": "feedback-smtp.example.net",
                      "priority": 10,
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "send.acmeplumbing.com",
                      "value": "v=spf1 include:spf.example.net ~all",
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "mail._domainkey.acmeplumbing.com",
                      "value": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...",
                      "state": "set"
                    }
                  ],
                  "dmarc_suggestion": {
                    "type": "TXT",
                    "name": "_dmarc.acmeplumbing.com",
                    "value": "v=DMARC1; p=none;"
                  },
                  "next_step": "The records are set. Call verify_email_domain in a few minutes; verification usually takes minutes, sometimes up to a few hours.",
                  "checked_at": null,
                  "verified_at": null,
                  "created_at": "2026-10-06T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, mail_not_enabled.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "mail_not_enabled"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/mail_not_enabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-domains/{id}": {
      "get": {
        "operationId": "getEmailDomain",
        "tags": [
          "Email"
        ],
        "summary": "Get a sending domain",
        "description": "One sending domain with its records and the status of the last check. Read-only; verify_email_domain runs a new check.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sending domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sending domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "verified",
                        "failed"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "dns": {
                      "type": "string",
                      "enum": [
                        "managed",
                        "manual"
                      ],
                      "description": "managed: the domain's DNS is here and we set the records; manual: the user adds them at their own DNS provider."
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          },
                          "priority": {
                            "type": "integer"
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "set",
                              "merged",
                              "exists",
                              "conflict",
                              "add_yourself"
                            ],
                            "description": "set: we added it; merged: added to the existing SPF record; exists: already right; conflict: not added, see note; add_yourself: add it at your DNS provider."
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value",
                          "state"
                        ]
                      }
                    },
                    "dmarc_suggestion": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "TXT"
                        },
                        "name": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "name",
                        "value"
                      ],
                      "description": "Only when the domain has no DMARC record yet. A suggestion; we never set it ourselves."
                    },
                    "next_step": {
                      "type": "string"
                    },
                    "checked_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "domain",
                    "status",
                    "test",
                    "dns",
                    "records",
                    "dmarc_suggestion",
                    "next_step",
                    "checked_at",
                    "verified_at",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "status": "pending",
                  "test": false,
                  "dns": "managed",
                  "records": [
                    {
                      "type": "MX",
                      "name": "send.acmeplumbing.com",
                      "value": "feedback-smtp.example.net",
                      "priority": 10,
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "send.acmeplumbing.com",
                      "value": "v=spf1 include:spf.example.net ~all",
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "mail._domainkey.acmeplumbing.com",
                      "value": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...",
                      "state": "set"
                    }
                  ],
                  "dmarc_suggestion": {
                    "type": "TXT",
                    "name": "_dmarc.acmeplumbing.com",
                    "value": "v=DMARC1; p=none;"
                  },
                  "next_step": "The records are set. Call verify_email_domain in a few minutes; verification usually takes minutes, sometimes up to a few hours.",
                  "checked_at": null,
                  "verified_at": null,
                  "created_at": "2026-10-06T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "removeEmailDomain",
        "tags": [
          "Email"
        ],
        "summary": "Remove a sending domain",
        "description": "Stops sending from this domain. The DNS records remain (they do no harm); the domain itself stays connected.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sending domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                },
                "example": {
                  "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-domains/{id}/verify": {
      "post": {
        "operationId": "verifyEmailDomain",
        "tags": [
          "Email"
        ],
        "summary": "Check a sending domain now",
        "description": "Checks the DNS records of the sending domain now and returns the new status: pending, verified or failed. At most 10 checks per minute per domain.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sending domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sending domain after the check.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "verified",
                        "failed"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "dns": {
                      "type": "string",
                      "enum": [
                        "managed",
                        "manual"
                      ],
                      "description": "managed: the domain's DNS is here and we set the records; manual: the user adds them at their own DNS provider."
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          },
                          "priority": {
                            "type": "integer"
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "set",
                              "merged",
                              "exists",
                              "conflict",
                              "add_yourself"
                            ],
                            "description": "set: we added it; merged: added to the existing SPF record; exists: already right; conflict: not added, see note; add_yourself: add it at your DNS provider."
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value",
                          "state"
                        ]
                      }
                    },
                    "dmarc_suggestion": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "TXT"
                        },
                        "name": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "name",
                        "value"
                      ],
                      "description": "Only when the domain has no DMARC record yet. A suggestion; we never set it ourselves."
                    },
                    "next_step": {
                      "type": "string"
                    },
                    "checked_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "domain",
                    "status",
                    "test",
                    "dns",
                    "records",
                    "dmarc_suggestion",
                    "next_step",
                    "checked_at",
                    "verified_at",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "status": "verified",
                  "test": false,
                  "dns": "managed",
                  "records": [
                    {
                      "type": "MX",
                      "name": "send.acmeplumbing.com",
                      "value": "feedback-smtp.example.net",
                      "priority": 10,
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "send.acmeplumbing.com",
                      "value": "v=spf1 include:spf.example.net ~all",
                      "state": "set"
                    },
                    {
                      "type": "TXT",
                      "name": "mail._domainkey.acmeplumbing.com",
                      "value": "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...",
                      "state": "set"
                    }
                  ],
                  "dmarc_suggestion": {
                    "type": "TXT",
                    "name": "_dmarc.acmeplumbing.com",
                    "value": "v=DMARC1; p=none;"
                  },
                  "next_step": "The records are set. Call verify_email_domain in a few minutes; verification usually takes minutes, sometimes up to a few hours.",
                  "checked_at": "2026-10-06T10:05:00.000Z",
                  "verified_at": "2026-10-06T10:05:00.000Z",
                  "created_at": "2026-10-06T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-recipients": {
      "post": {
        "operationId": "allowEmailRecipient",
        "tags": [
          "Email"
        ],
        "summary": "Confirm a recipient (owner only)",
        "description": "The owner confirms an address that does not appear in the project, so send_email may use it. Only through a connection where the owner is signed in (a chat), or in the portal; an API key cannot. Ask the owner first and only pass an address they typed or confirmed. At most 20 a day.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254,
                    "description": "The address the owner confirmed they want to email."
                  }
                },
                "required": [
                  "email"
                ],
                "additionalProperties": false
              },
              "example": {
                "email": "sam@example.org"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Confirmed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "type": "string"
                    },
                    "allowed": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "email",
                    "allowed"
                  ]
                },
                "example": {
                  "email": "sam@example.org",
                  "allowed": true
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests: rate_limited, email_limit_reached.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "x-hypamoon-errors": [
              "rate_limited",
              "email_limit_reached"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/rate_limited",
                            "https://hypamoon.com/docs/errors/email_limit_reached"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-recipients/check": {
      "post": {
        "operationId": "checkEmailAddress",
        "tags": [
          "Email"
        ],
        "summary": "Check email addresses",
        "description": "Checks one address or up to 50 the way sending does before the first email to someone: the format, this project's suppression list (an earlier hard bounce or spam complaint, or an unsubscribe), likely typos of big mail domains (gmial.com), throwaway domains, and whether the domain exists and accepts email (MX, or an A record without MX). Sends nothing and stores nothing. Use it on addresses from a form or an import before sending. At most 30 calls per minute per project.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 320,
                    "description": "One address to check."
                  },
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 320
                    },
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "Or a list of 1 to 50 addresses."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "emails": [
                  "sam@example.org",
                  "kim@gmial.com"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per address, in the order sent.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string"
                          },
                          "result": {
                            "type": "string",
                            "enum": [
                              "valid",
                              "risky",
                              "invalid",
                              "unknown"
                            ],
                            "description": "valid: deliverable; risky: it would bounce less often but has a catch (throwaway, unsubscribed, complained); invalid: an email would not arrive; unknown: the domain could not be checked right now."
                          },
                          "code": {
                            "type": "string",
                            "enum": [
                              "ok",
                              "invalid_syntax",
                              "domain_not_found",
                              "no_mail_server",
                              "disposable",
                              "likely_typo",
                              "hard_bounced",
                              "complained",
                              "unsubscribed",
                              "unknown"
                            ]
                          },
                          "reason": {
                            "type": "string",
                            "description": "In plain words."
                          },
                          "suggestion": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "With likely_typo: the address that was probably meant. Never use it without asking the user."
                          },
                          "would_send": {
                            "type": "object",
                            "properties": {
                              "transactional": {
                                "type": "boolean"
                              },
                              "other": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "transactional",
                              "other"
                            ],
                            "description": "Whether send_email would accept this address now, per category."
                          }
                        },
                        "required": [
                          "email",
                          "result",
                          "code",
                          "reason",
                          "suggestion",
                          "would_send"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "email": "sam@example.org",
                      "result": "valid",
                      "code": "ok",
                      "reason": "The address looks deliverable: its domain accepts email.",
                      "suggestion": null,
                      "would_send": {
                        "transactional": true,
                        "other": true
                      }
                    },
                    {
                      "email": "kim@gmial.com",
                      "result": "invalid",
                      "code": "likely_typo",
                      "reason": "This looks like a typo in the domain. Did you mean kim@gmail.com?",
                      "suggestion": "kim@gmail.com",
                      "would_send": {
                        "transactional": false,
                        "other": false
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-deliverability": {
      "get": {
        "operationId": "getEmailDeliverability",
        "tags": [
          "Email"
        ],
        "summary": "Get delivery per sending domain",
        "description": "Per sending domain over the last 7 and 30 days: emails sent, delivered, bounced and marked as spam, with the rates (counted per email). Mailboxes start filtering above 2% bounces or 0.1% spam complaints; health and advice say what to do in plain words. Opens are not measured. Also SPF and DKIM (pass once the domain is verified) and the DMARC record that is in the DNS now, and the state of a dedicated sending IP with its price. With a test key the numbers come from simulated test emails, so there is no delivery data. Read-only.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery per sending domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "test": {
                      "type": "boolean",
                      "description": "true with a test key: test emails are simulated, so there is no delivery data."
                    },
                    "thresholds": {
                      "type": "object",
                      "properties": {
                        "bounce_rate": {
                          "type": "number"
                        },
                        "complaint_rate": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "bounce_rate",
                        "complaint_rate"
                      ]
                    },
                    "opens_measured": {
                      "type": "boolean",
                      "const": false
                    },
                    "domains": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email_domain_id": {
                            "type": "string"
                          },
                          "domain": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "verified",
                              "failed"
                            ]
                          },
                          "authentication": {
                            "type": "object",
                            "properties": {
                              "spf": {
                                "type": "string",
                                "enum": [
                                  "pass",
                                  "not_verified"
                                ]
                              },
                              "dkim": {
                                "type": "string",
                                "enum": [
                                  "pass",
                                  "not_verified"
                                ]
                              },
                              "dmarc": {
                                "type": "object",
                                "properties": {
                                  "record": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "policy": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "none",
                                      "quarantine",
                                      "reject",
                                      null
                                    ]
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "enforced",
                                      "monitoring",
                                      "missing",
                                      "unknown"
                                    ]
                                  }
                                },
                                "required": [
                                  "record",
                                  "policy",
                                  "status"
                                ]
                              }
                            },
                            "required": [
                              "spf",
                              "dkim",
                              "dmarc"
                            ]
                          },
                          "last_7_days": {
                            "type": "object",
                            "properties": {
                              "sent": {
                                "type": "integer"
                              },
                              "delivered": {
                                "type": "integer"
                              },
                              "bounced": {
                                "type": "integer"
                              },
                              "complained": {
                                "type": "integer"
                              },
                              "opened": {
                                "type": "null",
                                "description": "Opens are not measured (no tracking pixel)."
                              },
                              "bounce_rate": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "bounced / sent, 0 to 1; null without emails."
                              },
                              "complaint_rate": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "complained / sent, 0 to 1; null without emails."
                              }
                            },
                            "required": [
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "opened",
                              "bounce_rate",
                              "complaint_rate"
                            ]
                          },
                          "last_30_days": {
                            "type": "object",
                            "properties": {
                              "sent": {
                                "type": "integer"
                              },
                              "delivered": {
                                "type": "integer"
                              },
                              "bounced": {
                                "type": "integer"
                              },
                              "complained": {
                                "type": "integer"
                              },
                              "opened": {
                                "type": "null",
                                "description": "Opens are not measured (no tracking pixel)."
                              },
                              "bounce_rate": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "bounced / sent, 0 to 1; null without emails."
                              },
                              "complaint_rate": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "complained / sent, 0 to 1; null without emails."
                              }
                            },
                            "required": [
                              "sent",
                              "delivered",
                              "bounced",
                              "complained",
                              "opened",
                              "bounce_rate",
                              "complaint_rate"
                            ]
                          },
                          "health": {
                            "type": "string",
                            "enum": [
                              "good",
                              "warning",
                              "problem",
                              "no_data"
                            ]
                          },
                          "advice": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "What to do, in plain words."
                          }
                        },
                        "required": [
                          "email_domain_id",
                          "domain",
                          "status",
                          "authentication",
                          "last_7_days",
                          "last_30_days",
                          "health",
                          "advice"
                        ]
                      }
                    },
                    "dedicated_ip": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "none",
                            "requested",
                            "active",
                            "declined"
                          ]
                        },
                        "requested_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "emails_last_30_days": {
                          "type": "integer"
                        },
                        "price": {
                          "type": "object",
                          "properties": {
                            "month_eur_per_ip": {
                              "type": "number"
                            },
                            "free": {
                              "type": "boolean"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "month_eur_per_ip",
                            "free",
                            "text"
                          ],
                          "description": "Per IP per month, excluding VAT; free for founder projects."
                        },
                        "advice": {
                          "type": "string"
                        },
                        "available": {
                          "type": "boolean",
                          "description": "Whether this project can request one (Business and Enterprise)."
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "status",
                        "requested_at",
                        "emails_last_30_days",
                        "price",
                        "advice",
                        "available",
                        "reason"
                      ]
                    }
                  },
                  "required": [
                    "test",
                    "thresholds",
                    "opens_measured",
                    "domains",
                    "dedicated_ip"
                  ]
                },
                "example": {
                  "test": false,
                  "thresholds": {
                    "bounce_rate": 0.02,
                    "complaint_rate": 0.001
                  },
                  "opens_measured": false,
                  "domains": [
                    {
                      "email_domain_id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                      "domain": "acmeplumbing.com",
                      "status": "verified",
                      "authentication": {
                        "spf": "pass",
                        "dkim": "pass",
                        "dmarc": {
                          "record": "v=DMARC1; p=none;",
                          "policy": "none",
                          "status": "monitoring"
                        }
                      },
                      "last_7_days": {
                        "sent": 120,
                        "delivered": 119,
                        "bounced": 1,
                        "complained": 0,
                        "opened": null,
                        "bounce_rate": 0.0083,
                        "complaint_rate": 0
                      },
                      "last_30_days": {
                        "sent": 480,
                        "delivered": 476,
                        "bounced": 4,
                        "complained": 0,
                        "opened": null,
                        "bounce_rate": 0.0083,
                        "complaint_rate": 0
                      },
                      "health": "good",
                      "advice": [
                        "DMARC on acmeplumbing.com only monitors (p=none). Once your emails pass for a few weeks, move to p=quarantine; that is also needed for your logo in the inbox."
                      ]
                    }
                  ],
                  "dedicated_ip": {
                    "status": "none",
                    "requested_at": null,
                    "emails_last_30_days": 480,
                    "price": {
                      "month_eur_per_ip": 49,
                      "free": false,
                      "text": "€49 a month per IP, excluding VAT, once we have set it up."
                    },
                    "advice": "A dedicated IP makes sense from about 100,000 emails a month with steady volume. Below that, an IP gets too little traffic to build its own reputation, and the shared pool usually delivers better.",
                    "available": true,
                    "reason": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-domains/{id}/bimi": {
      "get": {
        "operationId": "getEmailBimi",
        "tags": [
          "Email"
        ],
        "summary": "Get the inbox logo (BIMI) of a sending domain",
        "description": "Whether the domain can show its logo next to emails in the inbox (BIMI): the DMARC policy (it must be quarantine or reject for all mail), the BIMI record at default._bimi, and the steps that are left. Gmail shows the logo only with a mark certificate (VMC or CMC) that the user gets from a certificate authority; Apple Mail only with a VMC; Yahoo can show it without a certificate for domains with a good sending reputation, but that is not guaranteed. Read-only.",
        "x-hypamoon-scope": "sites:read",
        "security": [
          {
            "ApiKey": [
              "sites:read"
            ]
          },
          {
            "OAuth2": [
              "sites:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sending domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The logo settings of the domain.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email_domain_id": {
                      "type": "string"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "dmarc": {
                      "type": "object",
                      "properties": {
                        "record": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "policy": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "none",
                            "quarantine",
                            "reject",
                            null
                          ]
                        },
                        "percentage": {
                          "type": "number"
                        },
                        "ok": {
                          "type": "boolean",
                          "description": "quarantine or reject for all mail."
                        }
                      },
                      "required": [
                        "record",
                        "policy",
                        "percentage",
                        "ok"
                      ]
                    },
                    "bimi": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "record": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "logo_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "certificate_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "name",
                        "record",
                        "logo_url",
                        "certificate_url"
                      ]
                    },
                    "ready": {
                      "type": "boolean"
                    },
                    "dns_managed": {
                      "type": "boolean",
                      "description": "The DNS is hosted here, so set_email_bimi sets the record itself."
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "note": {
                      "type": "string",
                      "description": "Which mailboxes show the logo, and what they need."
                    }
                  },
                  "required": [
                    "email_domain_id",
                    "domain",
                    "dmarc",
                    "bimi",
                    "ready",
                    "dns_managed",
                    "steps",
                    "note"
                  ]
                },
                "example": {
                  "email_domain_id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "dmarc": {
                    "record": "v=DMARC1; p=quarantine; rua=mailto:dmarc@acmeplumbing.com",
                    "policy": "quarantine",
                    "percentage": 100,
                    "ok": true
                  },
                  "bimi": {
                    "name": "default._bimi.acmeplumbing.com",
                    "record": "v=BIMI1; l=https://acmeplumbing.com/logo.svg;",
                    "logo_url": "https://acmeplumbing.com/logo.svg",
                    "certificate_url": null
                  },
                  "ready": true,
                  "dns_managed": true,
                  "steps": [
                    "For Gmail and Apple Mail, get a mark certificate (VMC or CMC) for the logo and add its address as certificate_url."
                  ],
                  "note": "Gmail shows the logo only with a mark certificate (VMC or CMC) that you get from a certificate authority yourself; Apple Mail only with a VMC. Yahoo can show it without a certificate for domains with a good sending reputation, but that is not guaranteed."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "setEmailBimi",
        "tags": [
          "Email"
        ],
        "summary": "Set the inbox logo (BIMI) of a sending domain",
        "description": "Sets the BIMI record default._bimi.<domain> with the logo (and the mark certificate, if the user has one). First checks that DMARC is quarantine or reject for all mail (otherwise bimi_not_ready; we never change DMARC ourselves) and that the logo is an SVG Tiny PS file mailboxes accept: version 1.2 with baseProfile tiny-ps, a <title>, square, no scripts or external files, at most 32 KB, on an https address without a redirect. When the domain's DNS is hosted here we set or update the record; otherwise record_to_add is the record to add at the DNS provider.",
        "x-hypamoon-scope": "sites:write",
        "security": [
          {
            "ApiKey": [
              "sites:write"
            ]
          },
          {
            "OAuth2": [
              "sites:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sending domain.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "logo_url": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "The https address of your logo as an SVG Tiny PS file (square, with a <title>, at most 32 KB), for example on your own site."
                  },
                  "certificate_url": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Optional: the https address of your mark certificate (VMC or CMC, a .pem file). Gmail needs one; Apple Mail needs a VMC."
                  }
                },
                "required": [
                  "logo_url"
                ],
                "additionalProperties": false
              },
              "example": {
                "logo_url": "https://acmeplumbing.com/logo.svg"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The record is set (or record_to_add says what to add).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email_domain_id": {
                      "type": "string"
                    },
                    "domain": {
                      "type": "string"
                    },
                    "dmarc": {
                      "type": "object",
                      "properties": {
                        "record": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "policy": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "none",
                            "quarantine",
                            "reject",
                            null
                          ]
                        },
                        "percentage": {
                          "type": "number"
                        },
                        "ok": {
                          "type": "boolean",
                          "description": "quarantine or reject for all mail."
                        }
                      },
                      "required": [
                        "record",
                        "policy",
                        "percentage",
                        "ok"
                      ]
                    },
                    "bimi": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "record": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "logo_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "certificate_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "name",
                        "record",
                        "logo_url",
                        "certificate_url"
                      ]
                    },
                    "ready": {
                      "type": "boolean"
                    },
                    "dns_managed": {
                      "type": "boolean",
                      "description": "The DNS is hosted here, so set_email_bimi sets the record itself."
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "note": {
                      "type": "string",
                      "description": "Which mailboxes show the logo, and what they need."
                    },
                    "record_to_add": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "TXT"
                        },
                        "name": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type",
                        "name",
                        "value"
                      ],
                      "description": "When the DNS is hosted elsewhere: the record to add there. Null when we set it."
                    }
                  },
                  "required": [
                    "email_domain_id",
                    "domain",
                    "dmarc",
                    "bimi",
                    "ready",
                    "dns_managed",
                    "steps",
                    "note",
                    "record_to_add"
                  ]
                },
                "example": {
                  "email_domain_id": "8d2f3e4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
                  "domain": "acmeplumbing.com",
                  "dmarc": {
                    "record": "v=DMARC1; p=quarantine; rua=mailto:dmarc@acmeplumbing.com",
                    "policy": "quarantine",
                    "percentage": 100,
                    "ok": true
                  },
                  "bimi": {
                    "name": "default._bimi.acmeplumbing.com",
                    "record": "v=BIMI1; l=https://acmeplumbing.com/logo.svg;",
                    "logo_url": "https://acmeplumbing.com/logo.svg",
                    "certificate_url": null
                  },
                  "ready": true,
                  "dns_managed": true,
                  "steps": [
                    "For Gmail and Apple Mail, get a mark certificate (VMC or CMC) for the logo and add its address as certificate_url."
                  ],
                  "note": "Gmail shows the logo only with a mark certificate (VMC or CMC) that you get from a certificate authority yourself; Apple Mail only with a VMC. Yahoo can show it without a certificate for domains with a good sending reputation, but that is not guaranteed.",
                  "record_to_add": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: bimi_not_ready.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "bimi_not_ready"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/bimi_not_ready"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-dedicated-ip": {
      "post": {
        "operationId": "requestDedicatedIp",
        "tags": [
          "Email"
        ],
        "summary": "Request a dedicated sending IP",
        "description": "Asks Hypamoon to set up a dedicated IP address for this project's email. Available on the Business and Enterprise plans for €49 a month per IP, excluding VAT (shown in price before anyone requests; GET /email-deliverability has it too); otherwise dedicated_ip_not_available. We set it up ourselves and get back to the owner; nothing changes until then. It makes sense from about 100,000 emails a month with steady volume; below that the shared pool usually delivers better. A second request while one is open returns that one.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "Optional: anything we should know, like expected volume."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "note": "We send about 150,000 order emails a month."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already requested: the open request.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "requested",
                        "active",
                        "declined"
                      ]
                    },
                    "requested_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "emails_last_30_days": {
                      "type": "integer"
                    },
                    "price": {
                      "type": "object",
                      "properties": {
                        "month_eur_per_ip": {
                          "type": "number"
                        },
                        "free": {
                          "type": "boolean"
                        },
                        "text": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "month_eur_per_ip",
                        "free",
                        "text"
                      ],
                      "description": "Per IP per month, excluding VAT; free for founder projects."
                    },
                    "advice": {
                      "type": "string"
                    },
                    "available": {
                      "type": "boolean",
                      "description": "Whether this project can request one (Business and Enterprise)."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "status",
                    "requested_at",
                    "emails_last_30_days",
                    "price",
                    "advice",
                    "available",
                    "reason"
                  ]
                },
                "example": {
                  "status": "requested",
                  "requested_at": "2026-10-15T12:00:00.000Z",
                  "emails_last_30_days": 142300,
                  "price": {
                    "month_eur_per_ip": 49,
                    "free": false,
                    "text": "€49 a month per IP, excluding VAT, once we have set it up."
                  },
                  "advice": "A dedicated IP makes sense from about 100,000 emails a month with steady volume. Below that, an IP gets too little traffic to build its own reputation, and the shared pool usually delivers better.",
                  "available": true,
                  "reason": null
                }
              }
            }
          },
          "201": {
            "description": "Requested.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "requested",
                        "active",
                        "declined"
                      ]
                    },
                    "requested_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "emails_last_30_days": {
                      "type": "integer"
                    },
                    "price": {
                      "type": "object",
                      "properties": {
                        "month_eur_per_ip": {
                          "type": "number"
                        },
                        "free": {
                          "type": "boolean"
                        },
                        "text": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "month_eur_per_ip",
                        "free",
                        "text"
                      ],
                      "description": "Per IP per month, excluding VAT; free for founder projects."
                    },
                    "advice": {
                      "type": "string"
                    },
                    "available": {
                      "type": "boolean",
                      "description": "Whether this project can request one (Business and Enterprise)."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "status",
                    "requested_at",
                    "emails_last_30_days",
                    "price",
                    "advice",
                    "available",
                    "reason"
                  ]
                },
                "example": {
                  "status": "requested",
                  "requested_at": "2026-10-15T12:00:00.000Z",
                  "emails_last_30_days": 142300,
                  "price": {
                    "month_eur_per_ip": 49,
                    "free": false,
                    "text": "€49 a month per IP, excluding VAT, once we have set it up."
                  },
                  "advice": "A dedicated IP makes sense from about 100,000 emails a month with steady volume. Below that, an IP gets too little traffic to build its own reputation, and the shared pool usually delivers better.",
                  "available": true,
                  "reason": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, dedicated_ip_not_available.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "dedicated_ip_not_available"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/dedicated_ip_not_available"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-templates": {
      "get": {
        "operationId": "listEmailTemplates",
        "tags": [
          "Email"
        ],
        "summary": "List email templates",
        "description": "The project's email templates, newest first, with their variables.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The templates.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "subject": {
                            "type": "string"
                          },
                          "html": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "text": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "style": {
                            "type": "string",
                            "enum": [
                              "site",
                              "none"
                            ],
                            "description": "site: the name, logo and colors of the project's site around the email. none: sent as it is."
                          },
                          "site_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "variables": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "The variables in the subject and content, like name or order_number."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "subject",
                          "html",
                          "text",
                          "style",
                          "site_id",
                          "variables",
                          "created_at",
                          "updated_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                      "name": "Welcome",
                      "subject": "Welcome, {{name|there}}",
                      "html": "<p>Hi {{name|there}},</p><p>Thanks for signing up. Reply to this email if you have a question.</p>",
                      "text": null,
                      "style": "site",
                      "site_id": null,
                      "variables": [
                        "name"
                      ],
                      "created_at": "2026-10-15T10:00:00.000Z",
                      "updated_at": "2026-10-15T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createEmailTemplate",
        "tags": [
          "Email"
        ],
        "summary": "Create an email template",
        "description": "A template has a name, a subject and html or text with variables: {{name}}, or {{name|there}} with a fallback when the value is missing. Values in html are escaped. style site (default) puts the email in the style of the project's site: its name, logo and colors. Send it with POST /emails and template_id plus data, use it in an automation or a sequence, or preview it first. At most 200 templates per project.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "A name for the template, unique in the project, like \"Welcome\" or \"Order shipped\"."
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300,
                    "description": "The subject, with variables like {{name}}."
                  },
                  "html": {
                    "type": "string",
                    "maxLength": 200000,
                    "description": "The HTML body with variables, like <p>Hi {{name|there}},</p>. Values are escaped. No scripts or forms."
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "The plain text body with variables. Without it, the text comes from the html."
                  },
                  "style": {
                    "type": "string",
                    "enum": [
                      "site",
                      "none"
                    ],
                    "default": "site",
                    "description": "site (default): the email gets the name, logo and colors of the project's site around it. none: the html is sent as it is."
                  },
                  "site_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional. The site whose style to use; without it the project's first site."
                  }
                },
                "required": [
                  "name",
                  "subject"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Welcome",
                "subject": "Welcome, {{name|there}}",
                "html": "<p>Hi {{name|there}},</p><p>Thanks for signing up.</p>",
                "style": "site"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The template.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "subject": {
                      "type": "string"
                    },
                    "html": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "text": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "style": {
                      "type": "string",
                      "enum": [
                        "site",
                        "none"
                      ],
                      "description": "site: the name, logo and colors of the project's site around the email. none: sent as it is."
                    },
                    "site_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "variables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The variables in the subject and content, like name or order_number."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "subject",
                    "html",
                    "text",
                    "style",
                    "site_id",
                    "variables",
                    "created_at",
                    "updated_at"
                  ]
                },
                "example": {
                  "id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "name": "Welcome",
                  "subject": "Welcome, {{name|there}}",
                  "html": "<p>Hi {{name|there}},</p><p>Thanks for signing up. Reply to this email if you have a question.</p>",
                  "text": null,
                  "style": "site",
                  "site_id": null,
                  "variables": [
                    "name"
                  ],
                  "created_at": "2026-10-15T10:00:00.000Z",
                  "updated_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-templates/{id}": {
      "get": {
        "operationId": "getEmailTemplate",
        "tags": [
          "Email"
        ],
        "summary": "Get an email template",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the template.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "subject": {
                      "type": "string"
                    },
                    "html": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "text": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "style": {
                      "type": "string",
                      "enum": [
                        "site",
                        "none"
                      ],
                      "description": "site: the name, logo and colors of the project's site around the email. none: sent as it is."
                    },
                    "site_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "variables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The variables in the subject and content, like name or order_number."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "subject",
                    "html",
                    "text",
                    "style",
                    "site_id",
                    "variables",
                    "created_at",
                    "updated_at"
                  ]
                },
                "example": {
                  "id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "name": "Welcome",
                  "subject": "Welcome, {{name|there}}",
                  "html": "<p>Hi {{name|there}},</p><p>Thanks for signing up. Reply to this email if you have a question.</p>",
                  "text": null,
                  "style": "site",
                  "site_id": null,
                  "variables": [
                    "name"
                  ],
                  "created_at": "2026-10-15T10:00:00.000Z",
                  "updated_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateEmailTemplate",
        "tags": [
          "Email"
        ],
        "summary": "Update an email template",
        "description": "Changes only the fields you send. Emails that are scheduled with this template use the new version when they go out.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the template.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200000
                  },
                  "text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000
                  },
                  "style": {
                    "type": "string",
                    "enum": [
                      "site",
                      "none"
                    ]
                  },
                  "site_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "subject": "Welcome to Acme, {{name|there}}"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "subject": {
                      "type": "string"
                    },
                    "html": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "text": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "style": {
                      "type": "string",
                      "enum": [
                        "site",
                        "none"
                      ],
                      "description": "site: the name, logo and colors of the project's site around the email. none: sent as it is."
                    },
                    "site_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "variables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The variables in the subject and content, like name or order_number."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "subject",
                    "html",
                    "text",
                    "style",
                    "site_id",
                    "variables",
                    "created_at",
                    "updated_at"
                  ]
                },
                "example": {
                  "id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "name": "Welcome",
                  "subject": "Welcome, {{name|there}}",
                  "html": "<p>Hi {{name|there}},</p><p>Thanks for signing up. Reply to this email if you have a question.</p>",
                  "text": null,
                  "style": "site",
                  "site_id": null,
                  "variables": [
                    "name"
                  ],
                  "created_at": "2026-10-15T10:00:00.000Z",
                  "updated_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteEmailTemplate",
        "tags": [
          "Email"
        ],
        "summary": "Delete an email template",
        "description": "Not possible while an automation, a sequence or a list uses it (conflict); change those first.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the template.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                },
                "example": {
                  "id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-templates/{id}/preview": {
      "post": {
        "operationId": "previewEmailTemplate",
        "tags": [
          "Email"
        ],
        "summary": "Preview an email template",
        "description": "The template filled with data, in the style of the site, without sending anything. missing_variables lists what has no value.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the template.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 2000
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "description": "The values for the variables, like { \"name\": \"Sam\", \"order_number\": \"1042\" }. Text, numbers or true/false."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "data": {
                  "name": "Sam"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The subject, html and text as they would be sent.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subject": {
                      "type": "string"
                    },
                    "html": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "text": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "missing_variables": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Variables without a value in data and without a fallback ({{name|there}}); they come out empty."
                    }
                  },
                  "required": [
                    "subject",
                    "html",
                    "text",
                    "missing_variables"
                  ]
                },
                "example": {
                  "subject": "Welcome, Sam",
                  "html": "<!doctype html>...",
                  "text": "Hi Sam,\n\nThanks for signing up.",
                  "missing_variables": []
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-automations": {
      "get": {
        "operationId": "listEmailAutomations",
        "tags": [
          "Email"
        ],
        "summary": "List email automations",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The automations.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "event": {
                            "type": "string",
                            "enum": [
                              "form.submitted",
                              "booking.created",
                              "order.paid",
                              "order.payment_failed",
                              "app_user.created",
                              "list.subscribed"
                            ]
                          },
                          "template_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "sequence_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "to": {
                            "type": "string",
                            "description": "contact (the person of the event) or a fixed address."
                          },
                          "from": {
                            "type": "string"
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "delay_minutes": {
                            "type": "integer"
                          },
                          "filter": {
                            "type": "object",
                            "properties": {
                              "collection": {
                                "type": "string"
                              },
                              "app_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "list_id": {
                                "type": "string",
                                "format": "uuid"
                              }
                            }
                          },
                          "enabled": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "event",
                          "template_id",
                          "sequence_id",
                          "to",
                          "from",
                          "reply_to",
                          "category",
                          "delay_minutes",
                          "filter",
                          "enabled",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
                      "event": "form.submitted",
                      "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                      "sequence_id": null,
                      "to": "contact",
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "reply_to": "office@acmeplumbing.com",
                      "category": "transactional",
                      "delay_minutes": 0,
                      "filter": {
                        "collection": "contact"
                      },
                      "enabled": true,
                      "created_at": "2026-10-15T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createEmailAutomation",
        "tags": [
          "Email"
        ],
        "summary": "Create an email automation",
        "description": "When something happens, send a template (or start a sequence) to the person it happened to, or to a fixed address that appears in the project. Events: form.submitted (a form on one of the project's sites was sent; filter.collection for one form), booking.created (a form submission in the collection bookings, or the one in filter.collection), order.paid, order.payment_failed (the payment failed, expired or was canceled), app_user.created (someone became a user of an app; filter.app_id for one app). The variables are the fields of the form, or order_id, order_number, total and currency, plus email and name. delay_minutes waits, like 60 for an hour (at most 30 days). Every event sends at most once per automation, also when it is reported twice. The email follows all rules of POST /emails: category transactional (default) answers what the person did. A test project only simulates.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "form.submitted",
                      "booking.created",
                      "order.paid",
                      "order.payment_failed",
                      "app_user.created",
                      "list.subscribed"
                    ],
                    "description": "What happens."
                  },
                  "template_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The template to send. Give template_id or sequence_id."
                  },
                  "sequence_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Or: start this sequence for the person."
                  },
                  "to": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254,
                    "default": "contact",
                    "description": "contact (default): the person of the event (who sent the form, the buyer, the new app user). Or one fixed address that appears in the project, like the owner's, for a notice."
                  },
                  "from": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320,
                    "description": "The sender: an address on a verified sending domain of this project, optionally with a name, like \"Acme <hello@acme.com>\"."
                  },
                  "reply_to": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "Optional. Where replies go, like the owner's own mailbox."
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "other"
                    ],
                    "default": "transactional",
                    "description": "transactional (default): it answers what the person did. other: gets an unsubscribe link."
                  },
                  "delay_minutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 43200,
                    "default": 0,
                    "description": "Wait this long after the event, like 60 for one hour. 0 sends right away."
                  },
                  "filter": {
                    "type": "object",
                    "properties": {
                      "collection": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100,
                        "description": "form.submitted and booking.created: only submissions to this collection (the form)."
                      },
                      "app_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "app_user.created: only users of this app."
                      },
                      "list_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "list.subscribed: only this list."
                      }
                    },
                    "additionalProperties": false
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "event",
                  "from"
                ],
                "additionalProperties": false
              },
              "example": {
                "event": "form.submitted",
                "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                "from": "Acme Plumbing <hello@acmeplumbing.com>",
                "reply_to": "office@acmeplumbing.com",
                "filter": {
                  "collection": "contact"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The automation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "event": {
                      "type": "string",
                      "enum": [
                        "form.submitted",
                        "booking.created",
                        "order.paid",
                        "order.payment_failed",
                        "app_user.created",
                        "list.subscribed"
                      ]
                    },
                    "template_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "sequence_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "to": {
                      "type": "string",
                      "description": "contact (the person of the event) or a fixed address."
                    },
                    "from": {
                      "type": "string"
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "delay_minutes": {
                      "type": "integer"
                    },
                    "filter": {
                      "type": "object",
                      "properties": {
                        "collection": {
                          "type": "string"
                        },
                        "app_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "list_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "event",
                    "template_id",
                    "sequence_id",
                    "to",
                    "from",
                    "reply_to",
                    "category",
                    "delay_minutes",
                    "filter",
                    "enabled",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
                  "event": "form.submitted",
                  "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "sequence_id": null,
                  "to": "contact",
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "reply_to": "office@acmeplumbing.com",
                  "category": "transactional",
                  "delay_minutes": 0,
                  "filter": {
                    "collection": "contact"
                  },
                  "enabled": true,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: email_domain_not_verified, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "email_domain_not_verified",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/email_domain_not_verified",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-automations/{id}": {
      "patch": {
        "operationId": "updateEmailAutomation",
        "tags": [
          "Email"
        ],
        "summary": "Update an email automation",
        "description": "Changes only the fields you send, like enabled: false to pause it.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the automation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "to": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254
                  },
                  "from": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320,
                    "description": "The sender: an address on a verified sending domain of this project, optionally with a name, like \"Acme <hello@acme.com>\"."
                  },
                  "reply_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "other"
                    ]
                  },
                  "delay_minutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 43200
                  },
                  "filter": {
                    "type": "object",
                    "properties": {
                      "collection": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100,
                        "description": "form.submitted and booking.created: only submissions to this collection (the form)."
                      },
                      "app_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "app_user.created: only users of this app."
                      },
                      "list_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "list.subscribed: only this list."
                      }
                    },
                    "additionalProperties": false
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated automation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "event": {
                      "type": "string",
                      "enum": [
                        "form.submitted",
                        "booking.created",
                        "order.paid",
                        "order.payment_failed",
                        "app_user.created",
                        "list.subscribed"
                      ]
                    },
                    "template_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "sequence_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "to": {
                      "type": "string",
                      "description": "contact (the person of the event) or a fixed address."
                    },
                    "from": {
                      "type": "string"
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "delay_minutes": {
                      "type": "integer"
                    },
                    "filter": {
                      "type": "object",
                      "properties": {
                        "collection": {
                          "type": "string"
                        },
                        "app_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "list_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "event",
                    "template_id",
                    "sequence_id",
                    "to",
                    "from",
                    "reply_to",
                    "category",
                    "delay_minutes",
                    "filter",
                    "enabled",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
                  "event": "form.submitted",
                  "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                  "sequence_id": null,
                  "to": "contact",
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "reply_to": "office@acmeplumbing.com",
                  "category": "transactional",
                  "delay_minutes": 0,
                  "filter": {
                    "collection": "contact"
                  },
                  "enabled": false,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: email_domain_not_verified.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "email_domain_not_verified"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/email_domain_not_verified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteEmailAutomation",
        "tags": [
          "Email"
        ],
        "summary": "Delete an email automation",
        "description": "Emails it already scheduled still go; cancel those with POST /scheduled-emails/{id}/cancel.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the automation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                },
                "example": {
                  "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/scheduled-emails": {
      "get": {
        "operationId": "listScheduledEmails",
        "tags": [
          "Email"
        ],
        "summary": "List scheduled emails",
        "description": "Emails that go later or went: send_at, automations, sequence steps, list sends and confirmation emails, newest first, with their status.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only this status.",
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "sending",
                "sent",
                "canceled",
                "skipped",
                "failed"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "Only this source.",
            "schema": {
              "type": "string",
              "enum": [
                "api",
                "automation",
                "sequence",
                "list",
                "confirmation"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The scheduled emails.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The id of the scheduled email. cancel_scheduled_email takes it."
                          },
                          "source": {
                            "type": "string",
                            "enum": [
                              "api",
                              "automation",
                              "sequence",
                              "list",
                              "confirmation"
                            ],
                            "description": "api: send_email with send_at. automation, sequence, list: sent by those. confirmation: the confirmation email of a list."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "scheduled",
                              "sending",
                              "sent",
                              "canceled",
                              "skipped",
                              "failed"
                            ],
                            "description": "scheduled (waits for send_at), sending, sent (email_id is the email), canceled, skipped (the person unsubscribed or left the list or sequence), failed (reason says why)."
                          },
                          "send_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "subject": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "template_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "email_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The email once it was sent; get_email shows whether it was delivered."
                          },
                          "reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "source",
                          "status",
                          "send_at",
                          "to",
                          "subject",
                          "template_id",
                          "email_id",
                          "reason",
                          "test",
                          "created_at"
                        ]
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "next_cursor"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
                      "source": "api",
                      "status": "scheduled",
                      "send_at": "2026-10-16T08:00:00.000Z",
                      "to": "sam@example.org",
                      "subject": "Your appointment tomorrow",
                      "template_id": null,
                      "email_id": null,
                      "reason": null,
                      "test": false,
                      "created_at": "2026-10-15T10:00:00.000Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/scheduled-emails/{id}": {
      "get": {
        "operationId": "getScheduledEmail",
        "tags": [
          "Email"
        ],
        "summary": "Get a scheduled email",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the scheduled email.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The scheduled email.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the scheduled email. cancel_scheduled_email takes it."
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "api",
                        "automation",
                        "sequence",
                        "list",
                        "confirmation"
                      ],
                      "description": "api: send_email with send_at. automation, sequence, list: sent by those. confirmation: the confirmation email of a list."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "scheduled",
                        "sending",
                        "sent",
                        "canceled",
                        "skipped",
                        "failed"
                      ],
                      "description": "scheduled (waits for send_at), sending, sent (email_id is the email), canceled, skipped (the person unsubscribed or left the list or sequence), failed (reason says why)."
                    },
                    "send_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "template_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "email_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The email once it was sent; get_email shows whether it was delivered."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "source",
                    "status",
                    "send_at",
                    "to",
                    "subject",
                    "template_id",
                    "email_id",
                    "reason",
                    "test",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
                  "source": "api",
                  "status": "scheduled",
                  "send_at": "2026-10-16T08:00:00.000Z",
                  "to": "sam@example.org",
                  "subject": "Your appointment tomorrow",
                  "template_id": null,
                  "email_id": null,
                  "reason": null,
                  "test": false,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/scheduled-emails/{id}/cancel": {
      "post": {
        "operationId": "cancelScheduledEmail",
        "tags": [
          "Email"
        ],
        "summary": "Cancel a scheduled email",
        "description": "An email that is still scheduled will not be sent. Once it is sent (or sending) it cannot be canceled (conflict).",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the scheduled email.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Canceled.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The id of the scheduled email. cancel_scheduled_email takes it."
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "api",
                        "automation",
                        "sequence",
                        "list",
                        "confirmation"
                      ],
                      "description": "api: send_email with send_at. automation, sequence, list: sent by those. confirmation: the confirmation email of a list."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "scheduled",
                        "sending",
                        "sent",
                        "canceled",
                        "skipped",
                        "failed"
                      ],
                      "description": "scheduled (waits for send_at), sending, sent (email_id is the email), canceled, skipped (the person unsubscribed or left the list or sequence), failed (reason says why)."
                    },
                    "send_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "subject": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "template_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "email_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "The email once it was sent; get_email shows whether it was delivered."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "source",
                    "status",
                    "send_at",
                    "to",
                    "subject",
                    "template_id",
                    "email_id",
                    "reason",
                    "test",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
                  "source": "api",
                  "status": "canceled",
                  "send_at": "2026-10-16T08:00:00.000Z",
                  "to": "sam@example.org",
                  "subject": "Your appointment tomorrow",
                  "template_id": null,
                  "email_id": null,
                  "reason": "canceled",
                  "test": false,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-sequences": {
      "get": {
        "operationId": "listEmailSequences",
        "tags": [
          "Email"
        ],
        "summary": "List email sequences",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The sequences, with how many people are in each now.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "steps": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "template_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "wait_minutes": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "template_id",
                                "wait_minutes"
                              ]
                            }
                          },
                          "stop_on": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "form.submitted",
                                "booking.created",
                                "order.paid",
                                "order.payment_failed",
                                "app_user.created",
                                "list.subscribed"
                              ]
                            }
                          },
                          "from": {
                            "type": "string"
                          },
                          "reply_to": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "transactional",
                              "other"
                            ]
                          },
                          "active": {
                            "type": "integer",
                            "description": "How many people are in the sequence now."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "steps",
                          "stop_on",
                          "from",
                          "reply_to",
                          "category",
                          "active",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                      "name": "Onboarding",
                      "steps": [
                        {
                          "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                          "wait_minutes": 0
                        },
                        {
                          "template_id": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a1b",
                          "wait_minutes": 2880
                        }
                      ],
                      "stop_on": [
                        "order.paid"
                      ],
                      "from": "Acme Plumbing <hello@acmeplumbing.com>",
                      "reply_to": null,
                      "category": "other",
                      "active": 12,
                      "created_at": "2026-10-15T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createEmailSequence",
        "tags": [
          "Email"
        ],
        "summary": "Create an email sequence",
        "description": "A series of 1 to 20 emails, each a template with a wait before it (in minutes, at most 30 days). Someone joins with POST /email-sequences/{id}/enrollments or through an automation. It stops for a person when they unsubscribe (always), when an event in stop_on happens for their address (like order.paid), or with POST /email-sequences/{id}/stop. category other (default) gives every email an unsubscribe link.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "A name, like \"Onboarding\"."
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "template_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "wait_minutes": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 43200,
                          "description": "Minutes to wait before this step: after joining for the first step, after the previous email for the next."
                        }
                      },
                      "required": [
                        "template_id",
                        "wait_minutes"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "1 to 20 emails, each a template with a wait before it."
                  },
                  "stop_on": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "form.submitted",
                        "booking.created",
                        "order.paid",
                        "order.payment_failed",
                        "app_user.created",
                        "list.subscribed"
                      ]
                    },
                    "maxItems": 6,
                    "default": [],
                    "description": "Stop for a person when one of these happens for their address, like order.paid. Unsubscribing always stops."
                  },
                  "from": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320,
                    "description": "The sender: an address on a verified sending domain of this project, optionally with a name, like \"Acme <hello@acme.com>\"."
                  },
                  "reply_to": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "Optional. Where replies go, like the owner's own mailbox."
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "other"
                    ],
                    "default": "other",
                    "description": "other (default): every email has an unsubscribe link. transactional only for steps that follow up on something the person did."
                  }
                },
                "required": [
                  "name",
                  "steps",
                  "from"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Onboarding",
                "steps": [
                  {
                    "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                    "wait_minutes": 0
                  },
                  {
                    "template_id": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a1b",
                    "wait_minutes": 2880
                  }
                ],
                "stop_on": [
                  "order.paid"
                ],
                "from": "Acme Plumbing <hello@acmeplumbing.com>"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The sequence.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "template_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "wait_minutes": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "template_id",
                          "wait_minutes"
                        ]
                      }
                    },
                    "stop_on": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "form.submitted",
                          "booking.created",
                          "order.paid",
                          "order.payment_failed",
                          "app_user.created",
                          "list.subscribed"
                        ]
                      }
                    },
                    "from": {
                      "type": "string"
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "active": {
                      "type": "integer",
                      "description": "How many people are in the sequence now."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "steps",
                    "stop_on",
                    "from",
                    "reply_to",
                    "category",
                    "active",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "name": "Onboarding",
                  "steps": [
                    {
                      "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                      "wait_minutes": 0
                    },
                    {
                      "template_id": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a1b",
                      "wait_minutes": 2880
                    }
                  ],
                  "stop_on": [
                    "order.paid"
                  ],
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "reply_to": null,
                  "category": "other",
                  "active": 0,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: email_domain_not_verified, conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "email_domain_not_verified",
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/email_domain_not_verified",
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-sequences/{id}": {
      "get": {
        "operationId": "getEmailSequence",
        "tags": [
          "Email"
        ],
        "summary": "Get an email sequence",
        "description": "The sequence with its last 100 enrollments.",
        "x-hypamoon-scope": "messages:read",
        "security": [
          {
            "ApiKey": [
              "messages:read"
            ]
          },
          {
            "OAuth2": [
              "messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sequence.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sequence.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "template_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "wait_minutes": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "template_id",
                          "wait_minutes"
                        ]
                      }
                    },
                    "stop_on": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "form.submitted",
                          "booking.created",
                          "order.paid",
                          "order.payment_failed",
                          "app_user.created",
                          "list.subscribed"
                        ]
                      }
                    },
                    "from": {
                      "type": "string"
                    },
                    "reply_to": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "transactional",
                        "other"
                      ]
                    },
                    "active": {
                      "type": "integer",
                      "description": "How many people are in the sequence now."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "enrollments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "sequence_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "email": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "completed",
                              "stopped"
                            ]
                          },
                          "step": {
                            "type": "integer",
                            "description": "The step that is next (0 is the first)."
                          },
                          "next_send_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "stop_reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why it stopped: unsubscribed, stopped, or the event (like order.paid)."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "sequence_id",
                          "email",
                          "status",
                          "step",
                          "next_send_at",
                          "stop_reason",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "steps",
                    "stop_on",
                    "from",
                    "reply_to",
                    "category",
                    "active",
                    "created_at",
                    "enrollments"
                  ]
                },
                "example": {
                  "id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "name": "Onboarding",
                  "steps": [
                    {
                      "template_id": "3f1a2b3c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
                      "wait_minutes": 0
                    },
                    {
                      "template_id": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a1b",
                      "wait_minutes": 2880
                    }
                  ],
                  "stop_on": [
                    "order.paid"
                  ],
                  "from": "Acme Plumbing <hello@acmeplumbing.com>",
                  "reply_to": null,
                  "category": "other",
                  "active": 12,
                  "created_at": "2026-10-15T10:00:00.000Z",
                  "enrollments": [
                    {
                      "id": "9f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
                      "sequence_id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                      "email": "sam@example.org",
                      "status": "active",
                      "step": 0,
                      "next_send_at": "2026-10-15T10:00:00.000Z",
                      "stop_reason": null,
                      "created_at": "2026-10-15T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteEmailSequence",
        "tags": [
          "Email"
        ],
        "summary": "Delete an email sequence",
        "description": "Everyone in it stops; steps that were scheduled are not sent.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sequence.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                },
                "example": {
                  "id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-sequences/{id}/enrollments": {
      "post": {
        "operationId": "enrollInEmailSequence",
        "tags": [
          "Email"
        ],
        "summary": "Add someone to an email sequence",
        "description": "Only someone the project already knows, like any recipient (a form submission with in_reply_to, a document, an app user, a buyer, a confirmed list contact, or an address the owner confirmed). data fills the variables of every step. Someone who is already in the sequence stays where they are (200).",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sequence.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254,
                    "description": "The person's address. It must appear in the project, like any recipient."
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 2000
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "description": "The values for the variables, like { \"name\": \"Sam\", \"order_number\": \"1042\" }. Text, numbers or true/false."
                  },
                  "in_reply_to": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The form submission or chat conversation this person came from, when that is the only place the address appears."
                  }
                },
                "required": [
                  "email"
                ],
                "additionalProperties": false
              },
              "example": {
                "email": "sam@example.org",
                "data": {
                  "name": "Sam"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already in the sequence.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "sequence_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "completed",
                        "stopped"
                      ]
                    },
                    "step": {
                      "type": "integer",
                      "description": "The step that is next (0 is the first)."
                    },
                    "next_send_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "stop_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why it stopped: unsubscribed, stopped, or the event (like order.paid)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "sequence_id",
                    "email",
                    "status",
                    "step",
                    "next_send_at",
                    "stop_reason",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "9f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
                  "sequence_id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "email": "sam@example.org",
                  "status": "active",
                  "step": 0,
                  "next_send_at": "2026-10-15T10:00:00.000Z",
                  "stop_reason": null,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "Added; the first step goes after its wait.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "sequence_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "completed",
                        "stopped"
                      ]
                    },
                    "step": {
                      "type": "integer",
                      "description": "The step that is next (0 is the first)."
                    },
                    "next_send_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "stop_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why it stopped: unsubscribed, stopped, or the event (like order.paid)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "sequence_id",
                    "email",
                    "status",
                    "step",
                    "next_send_at",
                    "stop_reason",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "9f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
                  "sequence_id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "email": "sam@example.org",
                  "status": "active",
                  "step": 0,
                  "next_send_at": "2026-10-15T10:00:00.000Z",
                  "stop_reason": null,
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, recipient_not_allowed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "recipient_not_allowed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/recipient_not_allowed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/email-sequences/{id}/stop": {
      "post": {
        "operationId": "stopEmailSequence",
        "tags": [
          "Email"
        ],
        "summary": "Stop an email sequence for someone",
        "description": "For example when the person replied. Steps that were scheduled for them are not sent.",
        "x-hypamoon-scope": "messages:write",
        "security": [
          {
            "ApiKey": [
              "messages:write"
            ]
          },
          {
            "OAuth2": [
              "messages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the sequence.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254
                  }
                },
                "required": [
                  "email"
                ],
                "additionalProperties": false
              },
              "example": {
                "email": "sam@example.org"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stopped.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "sequence_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "completed",
                        "stopped"
                      ]
                    },
                    "step": {
                      "type": "integer",
                      "description": "The step that is next (0 is the first)."
                    },
                    "next_send_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "stop_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Why it stopped: unsubscribed, stopped, or the event (like order.paid)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "sequence_id",
                    "email",
                    "status",
                    "step",
                    "next_send_at",
                    "stop_reason",
                    "created_at"
                  ]
                },
                "example": {
                  "id": "9f0a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
                  "sequence_id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
                  "email": "sam@example.org",
                  "status": "stopped",
                  "step": 0,
                  "next_send_at": null,
                  "stop_reason": "stopped",
                  "created_at": "2026-10-15T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients": {
      "get": {
        "operationId": "listClientProjects",
        "tags": [
          "Agencies"
        ],
        "summary": "List your client projects",
        "description": "The client projects of your agency: pending access requests and active relations, newest first. With all=true also declined and ended ones.",
        "x-hypamoon-scope": "clients:read",
        "security": [
          {
            "ApiKey": [
              "clients:read"
            ]
          },
          {
            "OAuth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "all",
            "in": "query",
            "description": "Also include declined and ended relations.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client relations.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "const": "client_relation"
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "agency": {
                            "type": "object",
                            "properties": {
                              "project_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "project_id",
                              "name"
                            ]
                          },
                          "client": {
                            "type": "object",
                            "properties": {
                              "project_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "project_id",
                              "name"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "active",
                              "declined",
                              "ended"
                            ]
                          },
                          "stage": {
                            "type": "string",
                            "enum": [
                              "building",
                              "client_owned"
                            ]
                          },
                          "role": {
                            "type": "string",
                            "enum": [
                              "viewer",
                              "builder",
                              "admin"
                            ]
                          },
                          "max_mode": {
                            "type": "string",
                            "enum": [
                              "test",
                              "live"
                            ]
                          },
                          "four_eyes": {
                            "type": "boolean"
                          },
                          "payer": {
                            "type": "string",
                            "enum": [
                              "client",
                              "agency"
                            ]
                          },
                          "budget_eur": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "payer_change": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "to": {
                                "type": "string",
                                "enum": [
                                  "client",
                                  "agency"
                                ]
                              },
                              "proposed_by": {
                                "type": "string",
                                "enum": [
                                  "client",
                                  "agency"
                                ]
                              },
                              "proposed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "to",
                              "proposed_by",
                              "proposed_at"
                            ]
                          },
                          "owner_invite": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "email": {
                                "type": "string"
                              },
                              "expires_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "agency_keeps_access": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "email",
                              "expires_at",
                              "agency_keeps_access"
                            ]
                          },
                          "confirmed_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ended_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "transfer": {
                            "type": "object",
                            "properties": {
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "incoming",
                                  "outgoing"
                                ]
                              },
                              "agency": {
                                "type": "object",
                                "properties": {
                                  "project_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "project_id",
                                  "name"
                                ]
                              },
                              "relation_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "expires_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "direction",
                              "agency",
                              "relation_id",
                              "expires_at"
                            ]
                          },
                          "ended_via": {
                            "type": "string",
                            "enum": [
                              "client",
                              "agency",
                              "admin",
                              "handover",
                              "expired",
                              "cleanup"
                            ]
                          },
                          "cleanup": {
                            "type": "object",
                            "properties": {
                              "phase": {
                                "type": "string",
                                "enum": [
                                  "notified",
                                  "trash",
                                  "deleted"
                                ]
                              },
                              "trash_on": {
                                "type": "string"
                              },
                              "delete_on": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "phase",
                              "trash_on",
                              "delete_on"
                            ]
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "agency",
                          "client",
                          "status",
                          "stage",
                          "role",
                          "max_mode",
                          "four_eyes",
                          "payer",
                          "budget_eur",
                          "payer_change",
                          "owner_invite",
                          "confirmed_at",
                          "ended_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "data"
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "client_relation",
                      "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                      "agency": {
                        "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                        "name": "Studio North"
                      },
                      "client": {
                        "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                        "name": "Bakery Jansen"
                      },
                      "status": "active",
                      "stage": "building",
                      "role": "builder",
                      "max_mode": "test",
                      "four_eyes": false,
                      "payer": "client",
                      "budget_eur": null,
                      "payer_change": null,
                      "owner_invite": null,
                      "confirmed_at": null,
                      "ended_at": null,
                      "created_at": "2026-10-12T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createClientProject",
        "tags": [
          "Agencies"
        ],
        "summary": "Set up a project for a client",
        "description": "Creates a new project for a client in the building stage: your agency is its temporary owner through the relation, everything stays in test with Free limits, until the client accepts the owner invitation (POST /clients/{id}/owner-invite). The one-project-per-owner limit of signing up does not apply here.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "payer": {
                    "type": "string",
                    "enum": [
                      "client",
                      "agency"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Bakery Jansen",
                "payer": "agency"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new client project and its relation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "project": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "project"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "project": {
                    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/access-requests": {
      "post": {
        "operationId": "requestClientAccess",
        "tags": [
          "Agencies"
        ],
        "summary": "Ask a client for access to their project",
        "description": "Asks the owner of an existing project for access. The answer has confirm_url: a page in the portal where the owner sees what your agency can do, who pays and how to revoke, and accepts or declines. With payer agency your agency offers to pay the usage; the owner can still choose to pay himself. Nothing is emailed; give the link to the client. An open request is returned as it is (200).",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "payer": {
                    "type": "string",
                    "enum": [
                      "client",
                      "agency"
                    ]
                  }
                },
                "required": [
                  "project_id"
                ]
              },
              "example": {
                "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                "message": "As discussed on the phone.",
                "payer": "agency"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The open request or active relation that already existed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "confirm_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "confirm_url"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "client_owned",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "confirm_url": "https://app.hypamoon.com/agencies?request=6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716"
                }
              }
            }
          },
          "201": {
            "description": "The new request.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "confirm_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "confirm_url"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "pending",
                  "stage": "client_owned",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "confirm_url": "https://app.hypamoon.com/agencies?request=6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/owner-invite": {
      "post": {
        "operationId": "inviteClientOwner",
        "tags": [
          "Agencies"
        ],
        "summary": "Invite the client to own the project",
        "description": "Emails the client a link to become the owner of a project in the building stage. Inviting again replaces the previous link. The link works for 14 days and only for a signed-in user with this email address.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                },
                "required": [
                  "email"
                ]
              },
              "example": {
                "email": "owner@example.org"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The relation with the open invitation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": {
                    "email": "owner@example.org",
                    "expires_at": "2026-10-26T09:00:00.000Z",
                    "agency_keeps_access": true
                  },
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}": {
      "delete": {
        "operationId": "endClientRelation",
        "tags": [
          "Agencies"
        ],
        "summary": "Stop working on a client project",
        "description": "Ends your agency's access to a client project, or withdraws a pending request. What you built stays with the client. If your agency paid the usage, the client pays again from now on and the owner gets an email; payer_stopped says so. open_payments lists your payment links and subscriptions for this client that still run on your own payment account: cancel what should stop. A project in the building stage that nobody took over goes to the trash after 30 days and is deleted 30 days later, unless you restore it.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ended relation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "payer_stopped": {
                      "type": "boolean"
                    },
                    "open_payments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "enum": [
                              "client_payment_link",
                              "client_subscription"
                            ]
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "client_project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "open",
                              "paid",
                              "failed",
                              "expired",
                              "active",
                              "canceled"
                            ]
                          },
                          "amount_cents": {
                            "type": "integer"
                          },
                          "fee_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "interval": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "month",
                              "year",
                              null
                            ]
                          },
                          "paid_until": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "canceled_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "checkout_url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "paid_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "client_project_id",
                          "status",
                          "amount_cents",
                          "fee_cents",
                          "currency",
                          "description",
                          "test",
                          "checkout_url",
                          "paid_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "ended",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": "2026-10-20T09:00:00.000Z",
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "payer_stopped": false,
                  "open_payments": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/transfer": {
      "post": {
        "operationId": "transferProject",
        "tags": [
          "Agencies"
        ],
        "summary": "Hand over a client project",
        "description": "Hands over a project in the building stage. With to_email the client becomes the owner after accepting on their own confirmation page; agency_keeps_access says whether your agency keeps working on it (true) or stops (false, then the client pays from then on). With to_agency_project_id another agency takes it over after an owner or builder there accepts in their portal (Clients); your access ends at that moment. A project the client already owns cannot be handed over: the client chooses its agencies. The answer lists open_payments: your payment links and subscriptions for this client, to cancel what should stop.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "agency_keeps_access": {
                    "type": "boolean"
                  },
                  "to_agency_project_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              },
              "example": {
                "to_agency_project_id": "5c4b3a29-1807-4f6e-8d5c-4b3a29180706",
                "message": "As agreed, you take over the build."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The handover waits for the receiving side.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "transfer_relation": {
                      "type": "object",
                      "properties": {
                        "object": {
                          "type": "string",
                          "const": "client_relation"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "client": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "active",
                            "declined",
                            "ended"
                          ]
                        },
                        "stage": {
                          "type": "string",
                          "enum": [
                            "building",
                            "client_owned"
                          ]
                        },
                        "role": {
                          "type": "string",
                          "enum": [
                            "viewer",
                            "builder",
                            "admin"
                          ]
                        },
                        "max_mode": {
                          "type": "string",
                          "enum": [
                            "test",
                            "live"
                          ]
                        },
                        "four_eyes": {
                          "type": "boolean"
                        },
                        "payer": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "budget_eur": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "payer_change": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "to": {
                              "type": "string",
                              "enum": [
                                "client",
                                "agency"
                              ]
                            },
                            "proposed_by": {
                              "type": "string",
                              "enum": [
                                "client",
                                "agency"
                              ]
                            },
                            "proposed_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "to",
                            "proposed_by",
                            "proposed_at"
                          ]
                        },
                        "owner_invite": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "email": {
                              "type": "string"
                            },
                            "expires_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "agency_keeps_access": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "expires_at",
                            "agency_keeps_access"
                          ]
                        },
                        "confirmed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "ended_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "transfer": {
                          "type": "object",
                          "properties": {
                            "direction": {
                              "type": "string",
                              "enum": [
                                "incoming",
                                "outgoing"
                              ]
                            },
                            "agency": {
                              "type": "object",
                              "properties": {
                                "project_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "name": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "project_id",
                                "name"
                              ]
                            },
                            "relation_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "expires_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "direction",
                            "agency",
                            "relation_id",
                            "expires_at"
                          ]
                        },
                        "ended_via": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency",
                            "admin",
                            "handover",
                            "expired",
                            "cleanup"
                          ]
                        },
                        "cleanup": {
                          "type": "object",
                          "properties": {
                            "phase": {
                              "type": "string",
                              "enum": [
                                "notified",
                                "trash",
                                "deleted"
                              ]
                            },
                            "trash_on": {
                              "type": "string"
                            },
                            "delete_on": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "phase",
                            "trash_on",
                            "delete_on"
                          ]
                        }
                      },
                      "required": [
                        "object",
                        "id",
                        "agency",
                        "client",
                        "status",
                        "stage",
                        "role",
                        "max_mode",
                        "four_eyes",
                        "payer",
                        "budget_eur",
                        "payer_change",
                        "owner_invite",
                        "confirmed_at",
                        "ended_at",
                        "created_at"
                      ]
                    },
                    "open_payments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "enum": [
                              "client_payment_link",
                              "client_subscription"
                            ]
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "client_project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "open",
                              "paid",
                              "failed",
                              "expired",
                              "active",
                              "canceled"
                            ]
                          },
                          "amount_cents": {
                            "type": "integer"
                          },
                          "fee_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "interval": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "month",
                              "year",
                              null
                            ]
                          },
                          "paid_until": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "canceled_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "checkout_url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "paid_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "client_project_id",
                          "status",
                          "amount_cents",
                          "fee_cents",
                          "currency",
                          "description",
                          "test",
                          "checkout_url",
                          "paid_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "open_payments"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "transfer": {
                    "direction": "outgoing",
                    "agency": {
                      "project_id": "5c4b3a29-1807-4f6e-8d5c-4b3a29180706",
                      "name": "Studio South"
                    },
                    "relation_id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                    "expires_at": "2026-10-26T09:00:00.000Z"
                  },
                  "open_payments": []
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "cancelProjectTransfer",
        "tags": [
          "Agencies"
        ],
        "summary": "Cancel a handover",
        "description": "Withdraws an open owner invitation or an open handover to another agency for a project in the building stage. canceled says how many were withdrawn.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The relation, without the open handover.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "canceled": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "canceled"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "canceled": 1
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/restore": {
      "post": {
        "operationId": "restoreClientProject",
        "tags": [
          "Agencies"
        ],
        "summary": "Restore an ended client project",
        "description": "Brings back a project in the building stage that your agency ended and nobody took over, also from the trash, as long as it was not deleted yet. Your agency is its temporary owner again.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The new relation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/usage": {
      "get": {
        "operationId": "getClientUsage",
        "tags": [
          "Agencies"
        ],
        "summary": "Get the usage of a client project",
        "description": "Costs and usage per calendar month (UTC), oldest first: cost_eur with cost_by_source and cost_by_payer (client: the project's own credit, agency: paid by your agency), top_ups_eur and the app meters, plus the current payer and budget_eur. For invoicing your client with your own markup, or your own client portal.",
        "x-hypamoon-scope": "clients:read",
        "security": [
          {
            "ApiKey": [
              "clients:read"
            ]
          },
          {
            "OAuth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "months",
            "in": "query",
            "description": "How many months back, the current month included (at most 12).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12,
              "default": 3
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage per month.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_usage"
                    },
                    "project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "currency": {
                      "type": "string",
                      "const": "EUR"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "months": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "month": {
                            "type": "string"
                          },
                          "cost_eur": {
                            "type": "number"
                          },
                          "cost_by_source": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "number"
                            }
                          },
                          "cost_by_payer": {
                            "type": "object",
                            "properties": {
                              "client": {
                                "type": "number"
                              },
                              "agency": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "client",
                              "agency"
                            ]
                          },
                          "cost_by_payer_source": {
                            "type": "object",
                            "properties": {
                              "client": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "number"
                                }
                              },
                              "agency": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "number"
                                }
                              }
                            },
                            "required": [
                              "client",
                              "agency"
                            ]
                          },
                          "top_ups_eur": {
                            "type": "number"
                          },
                          "app_requests": {
                            "type": "number"
                          },
                          "app_errors": {
                            "type": "number"
                          },
                          "app_cpu_ms": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "month",
                          "cost_eur",
                          "cost_by_source",
                          "cost_by_payer",
                          "cost_by_payer_source",
                          "top_ups_eur",
                          "app_requests",
                          "app_errors",
                          "app_cpu_ms"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "project_id",
                    "currency",
                    "months"
                  ]
                },
                "example": {
                  "object": "client_usage",
                  "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "currency": "EUR",
                  "payer": "agency",
                  "budget_eur": 50,
                  "months": [
                    {
                      "month": "2026-10",
                      "cost_eur": 4.2,
                      "cost_by_source": {
                        "calls": 3.9,
                        "sms": 0.3
                      },
                      "cost_by_payer": {
                        "client": 0,
                        "agency": 4.2
                      },
                      "cost_by_payer_source": {
                        "client": {},
                        "agency": {
                          "calls": 3.9,
                          "sms": 0.3
                        }
                      },
                      "top_ups_eur": 0,
                      "app_requests": 1830,
                      "app_errors": 2,
                      "app_cpu_ms": 51200
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/payer": {
      "put": {
        "operationId": "setClientPayer",
        "tags": [
          "Agencies"
        ],
        "summary": "Change who pays for a client project",
        "description": "Proposes who pays the usage of a client project (client or agency), or agrees to what the client proposed. It only changes when both sides agree: the client accepts in the portal under Agencies. Choosing the current payer withdraws an open proposal. In the building stage your agency decides alone; the client sees it when taking over. applied tells whether it changed now.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "payer": {
                    "type": "string",
                    "enum": [
                      "client",
                      "agency"
                    ]
                  }
                },
                "required": [
                  "payer"
                ]
              },
              "example": {
                "payer": "agency"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The relation, with payer_change while it waits for the client.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    },
                    "applied": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at",
                    "applied"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "client_owned",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": {
                    "to": "agency",
                    "proposed_by": "agency",
                    "proposed_at": "2026-10-12T09:00:00.000Z"
                  },
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "applied": false
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/budget": {
      "put": {
        "operationId": "setClientBudget",
        "tags": [
          "Agencies"
        ],
        "summary": "Set the monthly budget for a client project",
        "description": "The most your agency pays for this client project per calendar month, in euros excluding VAT, or null for no budget of its own (your project's spending cap still applies). When it is reached, new paid usage in the project gets a 402 billing_blocked and your agency gets one email that month.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "monthly_budget_eur": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000
                  }
                },
                "required": [
                  "monthly_budget_eur"
                ]
              },
              "example": {
                "monthly_budget_eur": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The relation with the budget.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "active",
                  "stage": "building",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "agency",
                  "budget_eur": 50,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/invoice-lines": {
      "get": {
        "operationId": "exportClientInvoiceLines",
        "tags": [
          "Agencies"
        ],
        "summary": "Export invoice lines for a client project",
        "description": "The usage of a client project as invoice lines per month, payer and source, from the same numbers as GET /clients/{id}/usage, so you invoice your client in your own accounting with your own markup. format=csv returns text/csv. Hypamoon does not send the invoice.",
        "x-hypamoon-scope": "clients:read",
        "security": [
          {
            "ApiKey": [
              "clients:read"
            ]
          },
          {
            "OAuth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "months",
            "in": "query",
            "description": "How many months back, the current month included.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12,
              "default": 1
            }
          },
          {
            "name": "payer",
            "in": "query",
            "description": "Only the lines your agency paid, only those the client paid, or all.",
            "schema": {
              "type": "string",
              "enum": [
                "agency",
                "client",
                "all"
              ],
              "default": "all"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "json, or csv for a spreadsheet or import.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice lines.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "currency": {
                      "type": "string",
                      "const": "EUR"
                    },
                    "months": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "total_eur": {
                      "type": "number"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "month": {
                            "type": "string"
                          },
                          "project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "project_name": {
                            "type": "string"
                          },
                          "payer": {
                            "type": "string",
                            "enum": [
                              "client",
                              "agency"
                            ]
                          },
                          "source": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "amount_eur": {
                            "type": "number"
                          },
                          "currency": {
                            "type": "string",
                            "const": "EUR"
                          }
                        },
                        "required": [
                          "month",
                          "project_id",
                          "project_name",
                          "payer",
                          "source",
                          "description",
                          "amount_eur",
                          "currency"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "project_id",
                    "currency",
                    "months",
                    "total_eur",
                    "data"
                  ]
                },
                "example": {
                  "object": "list",
                  "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "currency": "EUR",
                  "months": [
                    "2026-10"
                  ],
                  "total_eur": 4.2,
                  "data": [
                    {
                      "month": "2026-10",
                      "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "project_name": "Bakery Jansen",
                      "payer": "agency",
                      "source": "calls",
                      "description": "Calls, Bakery Jansen, 2026-10",
                      "amount_eur": 3.9,
                      "currency": "EUR"
                    },
                    {
                      "month": "2026-10",
                      "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "project_name": "Bakery Jansen",
                      "payer": "agency",
                      "source": "sms",
                      "description": "Text messages, Bakery Jansen, 2026-10",
                      "amount_eur": 0.3,
                      "currency": "EUR"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/payment-links": {
      "post": {
        "operationId": "createClientPaymentLink",
        "tags": [
          "Agencies"
        ],
        "summary": "Create a payment link for a client",
        "description": "A one-time payment on your agency's own payment account (connected on your agency project), with the client project in the metadata. The money goes to your agency; Hypamoon keeps only the transaction fee you accepted when connecting. Give checkout_url to your client yourself; nothing is emailed. Test until payments on your agency project are live. Events: client_payment.paid and client_payment.failed on your webhooks.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "redirect_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 64
                  }
                },
                "required": [
                  "amount_cents",
                  "description",
                  "redirect_url"
                ]
              },
              "example": {
                "amount_cents": 12100,
                "currency": "EUR",
                "description": "Website maintenance October",
                "redirect_url": "https://portal.studionorth.example/paid",
                "idempotency_key": "inv-2026-10-bakery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment link made earlier with this idempotency_key.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_payment_link",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "open",
                  "amount_cents": 12100,
                  "fee_cents": 131,
                  "currency": "EUR",
                  "description": "Website maintenance October",
                  "test": true,
                  "checkout_url": "https://checkout.example/pay/7UhSN1zuXS",
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "The payment link.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_payment_link",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "open",
                  "amount_cents": 12100,
                  "fee_cents": 131,
                  "currency": "EUR",
                  "description": "Website maintenance October",
                  "test": true,
                  "checkout_url": "https://checkout.example/pay/7UhSN1zuXS",
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/subscriptions": {
      "post": {
        "operationId": "createClientSubscription",
        "tags": [
          "Agencies"
        ],
        "summary": "Create a subscription for a client",
        "description": "A subscription on your agency's own payment account, with the client project in the metadata: the payer pays the first period through checkout_url, which also sets up the mandate; after that the payment provider charges every month or year. Not every payment account supports this yet; a 409 says so. Events: client_subscription.active, client_subscription.payment_paid, client_subscription.payment_failed and client_subscription.canceled.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "redirect_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 64
                  },
                  "interval": {
                    "type": "string",
                    "enum": [
                      "month",
                      "year"
                    ]
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  }
                },
                "required": [
                  "amount_cents",
                  "description",
                  "redirect_url",
                  "interval",
                  "email"
                ]
              },
              "example": {
                "amount_cents": 4900,
                "currency": "EUR",
                "interval": "month",
                "description": "Hosting and support",
                "email": "owner@example.org",
                "name": "Bakery Jansen",
                "redirect_url": "https://portal.studionorth.example/subscribed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The subscription made earlier with this idempotency_key.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_subscription",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "open",
                  "amount_cents": 12100,
                  "fee_cents": 131,
                  "currency": "EUR",
                  "description": "Website maintenance October",
                  "test": true,
                  "checkout_url": "https://checkout.example/pay/7UhSN1zuXS",
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "interval": "month",
                  "paid_until": null,
                  "canceled_at": null
                }
              }
            }
          },
          "201": {
            "description": "The subscription, open until the first payment.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_subscription",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "open",
                  "amount_cents": 4900,
                  "fee_cents": 59,
                  "currency": "EUR",
                  "description": "Hosting and support",
                  "test": true,
                  "checkout_url": "https://checkout.example/pay/7UhSN1zuXS",
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "interval": "month",
                  "paid_until": null,
                  "canceled_at": null
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_json"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_json"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Bad Gateway: upstream_error.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "upstream_error"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/upstream_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/payments": {
      "get": {
        "operationId": "listClientPayments",
        "tags": [
          "Agencies"
        ],
        "summary": "List payment links and subscriptions for a client",
        "description": "The payment links and subscriptions your agency made for this client project, newest first. Without the payer's email or name.",
        "x-hypamoon-scope": "clients:read",
        "security": [
          {
            "ApiKey": [
              "clients:read"
            ]
          },
          {
            "OAuth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "How many.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payments.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "enum": [
                              "client_payment_link",
                              "client_subscription"
                            ]
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "client_project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "open",
                              "paid",
                              "failed",
                              "expired",
                              "active",
                              "canceled"
                            ]
                          },
                          "amount_cents": {
                            "type": "integer"
                          },
                          "fee_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "interval": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "month",
                              "year",
                              null
                            ]
                          },
                          "paid_until": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "canceled_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "test": {
                            "type": "boolean"
                          },
                          "checkout_url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "paid_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "client_project_id",
                          "status",
                          "amount_cents",
                          "fee_cents",
                          "currency",
                          "description",
                          "test",
                          "checkout_url",
                          "paid_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "data"
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "client_payment_link",
                      "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                      "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "status": "open",
                      "amount_cents": 12100,
                      "fee_cents": 131,
                      "currency": "EUR",
                      "description": "Website maintenance October",
                      "test": true,
                      "checkout_url": "https://checkout.example/pay/7UhSN1zuXS",
                      "paid_at": null,
                      "created_at": "2026-10-12T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/subscriptions/{subscriptionId}": {
      "delete": {
        "operationId": "cancelClientSubscription",
        "tags": [
          "Agencies"
        ],
        "summary": "Cancel a client subscription",
        "description": "Stops a subscription at your payment provider right away: no more charges. What was paid stays paid; nothing is refunded.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "subscriptionId",
            "in": "path",
            "required": true,
            "description": "The id of the subscription.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The canceled subscription.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_subscription",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "canceled",
                  "amount_cents": 12100,
                  "fee_cents": 131,
                  "currency": "EUR",
                  "description": "Website maintenance October",
                  "test": true,
                  "checkout_url": null,
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z",
                  "interval": "month",
                  "paid_until": "2026-11-12T09:00:00.000Z",
                  "canceled_at": "2026-10-20T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflict: conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "conflict"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/conflict"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/payment-links/{linkId}": {
      "delete": {
        "operationId": "cancelClientPaymentLink",
        "tags": [
          "Agencies"
        ],
        "summary": "Withdraw an open payment link",
        "description": "Marks an open payment link of your agency as expired, also after your relation with the client ended. The checkout at your payment provider expires on its own; if it is still paid, the money is on your own account.",
        "x-hypamoon-scope": "clients:write",
        "security": [
          {
            "ApiKey": [
              "clients:write"
            ]
          },
          {
            "OAuth2": [
              "clients:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "linkId",
            "in": "path",
            "required": true,
            "description": "The id of the payment link.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The withdrawn payment link.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "client_payment_link",
                        "client_subscription"
                      ]
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "paid",
                        "failed",
                        "expired",
                        "active",
                        "canceled"
                      ]
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "fee_cents": {
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "month",
                        "year",
                        null
                      ]
                    },
                    "paid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "canceled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "test": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "paid_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "client_project_id",
                    "status",
                    "amount_cents",
                    "fee_cents",
                    "currency",
                    "description",
                    "test",
                    "checkout_url",
                    "paid_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_payment_link",
                  "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
                  "client_project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "status": "expired",
                  "amount_cents": 12100,
                  "fee_cents": 131,
                  "currency": "EUR",
                  "description": "Website maintenance October",
                  "test": true,
                  "checkout_url": null,
                  "paid_at": null,
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/clients/{id}/activity": {
      "get": {
        "operationId": "getClientActivity",
        "tags": [
          "Agencies"
        ],
        "summary": "Read the activity log of a client project",
        "description": "The activity log of a client project, newest first. Actions by your agency carry details.via_bureau. For the next page, pass next_before as before.",
        "x-hypamoon-scope": "clients:read",
        "security": [
          {
            "ApiKey": [
              "clients:read"
            ]
          },
          {
            "OAuth2": [
              "clients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the client project.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "How many entries.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "next_before from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The log entries.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "project_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "next_before": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "project_id",
                    "data",
                    "next_before"
                  ]
                },
                "example": {
                  "object": "list",
                  "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "data": [],
                  "next_before": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content: validation_failed.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "validation_failed"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/validation_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agencies": {
      "get": {
        "operationId": "listAgencies",
        "tags": [
          "Agencies"
        ],
        "summary": "List the agencies on this project",
        "description": "For a client: the agencies that work on this project or asked to, newest first. Accepting a request is only possible in the portal, where the owner reads what the agency can do.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The agency relations of this project.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "object": {
                            "type": "string",
                            "const": "client_relation"
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "agency": {
                            "type": "object",
                            "properties": {
                              "project_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "project_id",
                              "name"
                            ]
                          },
                          "client": {
                            "type": "object",
                            "properties": {
                              "project_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "project_id",
                              "name"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "active",
                              "declined",
                              "ended"
                            ]
                          },
                          "stage": {
                            "type": "string",
                            "enum": [
                              "building",
                              "client_owned"
                            ]
                          },
                          "role": {
                            "type": "string",
                            "enum": [
                              "viewer",
                              "builder",
                              "admin"
                            ]
                          },
                          "max_mode": {
                            "type": "string",
                            "enum": [
                              "test",
                              "live"
                            ]
                          },
                          "four_eyes": {
                            "type": "boolean"
                          },
                          "payer": {
                            "type": "string",
                            "enum": [
                              "client",
                              "agency"
                            ]
                          },
                          "budget_eur": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "payer_change": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "to": {
                                "type": "string",
                                "enum": [
                                  "client",
                                  "agency"
                                ]
                              },
                              "proposed_by": {
                                "type": "string",
                                "enum": [
                                  "client",
                                  "agency"
                                ]
                              },
                              "proposed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "to",
                              "proposed_by",
                              "proposed_at"
                            ]
                          },
                          "owner_invite": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "email": {
                                "type": "string"
                              },
                              "expires_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "agency_keeps_access": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "email",
                              "expires_at",
                              "agency_keeps_access"
                            ]
                          },
                          "confirmed_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ended_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "transfer": {
                            "type": "object",
                            "properties": {
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "incoming",
                                  "outgoing"
                                ]
                              },
                              "agency": {
                                "type": "object",
                                "properties": {
                                  "project_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "project_id",
                                  "name"
                                ]
                              },
                              "relation_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "expires_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "direction",
                              "agency",
                              "relation_id",
                              "expires_at"
                            ]
                          },
                          "ended_via": {
                            "type": "string",
                            "enum": [
                              "client",
                              "agency",
                              "admin",
                              "handover",
                              "expired",
                              "cleanup"
                            ]
                          },
                          "cleanup": {
                            "type": "object",
                            "properties": {
                              "phase": {
                                "type": "string",
                                "enum": [
                                  "notified",
                                  "trash",
                                  "deleted"
                                ]
                              },
                              "trash_on": {
                                "type": "string"
                              },
                              "delete_on": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "phase",
                              "trash_on",
                              "delete_on"
                            ]
                          }
                        },
                        "required": [
                          "object",
                          "id",
                          "agency",
                          "client",
                          "status",
                          "stage",
                          "role",
                          "max_mode",
                          "four_eyes",
                          "payer",
                          "budget_eur",
                          "payer_change",
                          "owner_invite",
                          "confirmed_at",
                          "ended_at",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "data"
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "client_relation",
                      "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                      "agency": {
                        "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                        "name": "Studio North"
                      },
                      "client": {
                        "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                        "name": "Bakery Jansen"
                      },
                      "status": "active",
                      "stage": "client_owned",
                      "role": "builder",
                      "max_mode": "test",
                      "four_eyes": false,
                      "payer": "client",
                      "budget_eur": null,
                      "payer_change": null,
                      "owner_invite": null,
                      "confirmed_at": null,
                      "ended_at": null,
                      "created_at": "2026-10-12T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/agencies/{id}": {
      "delete": {
        "operationId": "revokeAgency",
        "tags": [
          "Agencies"
        ],
        "summary": "Revoke an agency's access",
        "description": "For the owner of a client project: ends an agency's access right away, or declines its open request. What the agency built stays.",
        "x-hypamoon-scope": "project:write",
        "security": [
          {
            "ApiKey": [
              "project:write"
            ]
          },
          {
            "OAuth2": [
              "project:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the agency relation.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ended relation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "client_relation"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agency": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "client": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "name"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "declined",
                        "ended"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "building",
                        "client_owned"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "builder",
                        "admin"
                      ]
                    },
                    "max_mode": {
                      "type": "string",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "four_eyes": {
                      "type": "boolean"
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency"
                      ]
                    },
                    "budget_eur": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "payer_change": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "to": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_by": {
                          "type": "string",
                          "enum": [
                            "client",
                            "agency"
                          ]
                        },
                        "proposed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "to",
                        "proposed_by",
                        "proposed_at"
                      ]
                    },
                    "owner_invite": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "agency_keeps_access": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "email",
                        "expires_at",
                        "agency_keeps_access"
                      ]
                    },
                    "confirmed_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "transfer": {
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "incoming",
                            "outgoing"
                          ]
                        },
                        "agency": {
                          "type": "object",
                          "properties": {
                            "project_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "project_id",
                            "name"
                          ]
                        },
                        "relation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "expires_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "direction",
                        "agency",
                        "relation_id",
                        "expires_at"
                      ]
                    },
                    "ended_via": {
                      "type": "string",
                      "enum": [
                        "client",
                        "agency",
                        "admin",
                        "handover",
                        "expired",
                        "cleanup"
                      ]
                    },
                    "cleanup": {
                      "type": "object",
                      "properties": {
                        "phase": {
                          "type": "string",
                          "enum": [
                            "notified",
                            "trash",
                            "deleted"
                          ]
                        },
                        "trash_on": {
                          "type": "string"
                        },
                        "delete_on": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "phase",
                        "trash_on",
                        "delete_on"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "id",
                    "agency",
                    "client",
                    "status",
                    "stage",
                    "role",
                    "max_mode",
                    "four_eyes",
                    "payer",
                    "budget_eur",
                    "payer_change",
                    "owner_invite",
                    "confirmed_at",
                    "ended_at",
                    "created_at"
                  ]
                },
                "example": {
                  "object": "client_relation",
                  "id": "6e5d4c3b-2a19-4f8e-9d7c-6b5a49382716",
                  "agency": {
                    "project_id": "3f2a1c4e-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
                    "name": "Studio North"
                  },
                  "client": {
                    "project_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                    "name": "Bakery Jansen"
                  },
                  "status": "ended",
                  "stage": "client_owned",
                  "role": "builder",
                  "max_mode": "test",
                  "four_eyes": false,
                  "payer": "client",
                  "budget_eur": null,
                  "payer_change": null,
                  "owner_invite": null,
                  "confirmed_at": null,
                  "ended_at": "2026-10-20T09:00:00.000Z",
                  "created_at": "2026-10-12T09:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required, forbidden.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "project_not_enabled",
              "key_mode_mismatch",
              "missing_scope",
              "verification_required",
              "forbidden"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/project_not_enabled",
                            "https://hypamoon.com/docs/errors/key_mode_mismatch",
                            "https://hypamoon.com/docs/errors/missing_scope",
                            "https://hypamoon.com/docs/errors/verification_required",
                            "https://hypamoon.com/docs/errors/forbidden"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not Found: not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "not_found"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/errors": {
      "get": {
        "operationId": "listRecentErrors",
        "tags": [
          "Project"
        ],
        "summary": "List recent errors",
        "description": "The failed MCP tool calls and API requests of this project (live and test) from the last 14 days, newest first: when, which app (chatgpt, claude, grok, oauth, mcp or api), the tool or route, the error type, the status, the detail the caller got, how long it took and the request id. Arguments are only summarized (field names, types, lengths and counts), never their values; email addresses, phone numbers, keys and URL paths in the detail are masked. Pass request_id to find one error the user mentions. At most 1000 errors per project per day are kept; dropped_today counts the ones above that.",
        "x-hypamoon-scope": "project:read",
        "security": [
          {
            "ApiKey": [
              "project:read"
            ]
          },
          {
            "OAuth2": [
              "project:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Errors per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "next_before from the previous page, for older errors.",
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "request_id",
            "in": "query",
            "description": "Only the errors of this request (the request id from an error message or the X-Request-Id header).",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "tool",
            "in": "query",
            "description": "Only the errors of this MCP tool, like deploy_site.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The errors, newest first.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecentErrorList"
                },
                "example": {
                  "data": [
                    {
                      "id": "48213",
                      "at": "2026-10-12T14:03:11.402Z",
                      "source": "mcp",
                      "client": "chatgpt",
                      "tool": "deploy_site",
                      "route": null,
                      "error": "validation_failed",
                      "status": 422,
                      "detail": "Some files could not be fetched. See 'errors'.",
                      "duration_ms": 8123,
                      "request_id": "req_7Hq2LmX9pRt4VwZ1aB3c",
                      "mode": "live",
                      "arguments": {
                        "name": "string(12)",
                        "files": {
                          "type": "array",
                          "n": 14,
                          "fields": {
                            "path": 14,
                            "content": 11,
                            "source_url": 3
                          },
                          "chars": 48211
                        }
                      },
                      "field_errors": [
                        {
                          "path": "files.12.source_url",
                          "message": "images.example.com answered HTTP 403: it blocks downloads by servers. Upload the file with create_upload_urls instead."
                        }
                      ]
                    }
                  ],
                  "next_before": null,
                  "dropped_today": 0
                }
              }
            }
          },
          "400": {
            "description": "Bad Request: invalid_parameter.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "x-hypamoon-errors": [
              "invalid_parameter"
            ],
            "content": {
              "application/problem+json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Problem"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "https://hypamoon.com/docs/errors/invalid_parameter"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "webhooks": {
    "call.completed": {
      "post": {
        "operationId": "callCompleted",
        "tags": [
          "Webhooks"
        ],
        "summary": "A call ended",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallCompletedEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:15:38.000Z",
                "type": "call.completed",
                "data": {
                  "call": {
                    "id": "c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                    "direction": "inbound",
                    "from": "+447700900456",
                    "to": "+•••••••••42",
                    "status": "ai_handled",
                    "created_at": "2026-10-01T09:14:02.000Z",
                    "started_at": "2026-10-01T09:14:02.000Z",
                    "answered_at": "2026-10-01T09:14:03.000Z",
                    "ended_at": "2026-10-01T09:15:37.000Z",
                    "duration_sec": 94,
                    "hangup_cause": "normal_clearing",
                    "has_summary": true,
                    "summary_ready_at": null,
                    "test": false,
                    "outbound": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "call.summary.ready": {
      "post": {
        "operationId": "callSummaryReady",
        "tags": [
          "Webhooks"
        ],
        "summary": "The summary of a call is ready",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallSummaryReadyEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:15:38.000Z",
                "type": "call.summary.ready",
                "data": {
                  "call": {
                    "id": "c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                    "direction": "inbound",
                    "from": "+447700900456",
                    "to": "+•••••••••42",
                    "status": "ai_handled",
                    "created_at": "2026-10-01T09:14:02.000Z",
                    "started_at": "2026-10-01T09:14:02.000Z",
                    "answered_at": "2026-10-01T09:14:03.000Z",
                    "ended_at": "2026-10-01T09:15:37.000Z",
                    "duration_sec": 94,
                    "hangup_cause": "normal_clearing",
                    "has_summary": true,
                    "summary_ready_at": null,
                    "test": false,
                    "outbound": null
                  },
                  "summary": {
                    "text": "The caller has a leaking kitchen tap and wants someone to come today.",
                    "topics": [
                      "leak",
                      "appointment"
                    ],
                    "sentiment": "neutral",
                    "action_items": [
                      "Call back within the hour to plan a visit."
                    ],
                    "next_step": "Plan a visit today."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "message.delivered": {
      "post": {
        "operationId": "messageDelivered",
        "tags": [
          "Webhooks"
        ],
        "summary": "An SMS was delivered",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageDeliveredEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:00:04.000Z",
                "type": "message.delivered",
                "data": {
                  "message": {
                    "id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                    "to": "+447700900321",
                    "from": "Acme Shop",
                    "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                    "segments": 1,
                    "encoding": "gsm7",
                    "status": "delivered",
                    "error": null,
                    "reference": "order-10432",
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "status_updated_at": "2026-10-01T09:00:04.000Z",
                    "test": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "message.failed": {
      "post": {
        "operationId": "messageFailed",
        "tags": [
          "Webhooks"
        ],
        "summary": "An SMS could not be delivered",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageFailedEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:00:04.000Z",
                "type": "message.failed",
                "data": {
                  "message": {
                    "id": "8f7e6d5c-4b3a-4291-8f0e-1d2c3b4a5968",
                    "to": "+447700900321",
                    "from": "Acme Shop",
                    "body": "Hi Sam, thanks for your order 10432 at Acme Shop. We are packing it now; you get a track and trace link as soon as it ships.",
                    "segments": 1,
                    "encoding": "gsm7",
                    "status": "failed",
                    "error": "delivery_failed",
                    "reference": "order-10432",
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "status_updated_at": "2026-10-01T09:00:04.000Z",
                    "test": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "conversation.summary.ready": {
      "post": {
        "operationId": "conversationSummaryReady",
        "tags": [
          "Webhooks"
        ],
        "summary": "The summary of a chat conversation is ready",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationSummaryReadyEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:31:10.000Z",
                "type": "conversation.summary.ready",
                "data": {
                  "conversation": {
                    "id": "2c9d8e7f-6a5b-4c3d-9e2f-1a0b9c8d7e6f",
                    "channel": "chat",
                    "status": "closed",
                    "reference": "session-8841",
                    "metadata": {
                      "page": "/pricing",
                      "plan": "pro"
                    },
                    "customer": {
                      "name": "Sam Jones",
                      "phone": null,
                      "email": "sam@example.com"
                    },
                    "language": "en",
                    "turns": 2,
                    "max_turns": 30,
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "last_message_at": "2026-10-01T09:01:05.000Z",
                    "closed_at": "2026-10-01T09:31:05.000Z",
                    "close_reason": "inactive",
                    "has_summary": true,
                    "summary_ready_at": "2026-10-01T09:31:09.000Z",
                    "test": false
                  },
                  "summary": {
                    "text": "Sam Jones has a leaking kitchen tap and wants someone to come today. Sam left the number +447700900321 for a call back.",
                    "topics": [
                      "leak",
                      "visit"
                    ],
                    "sentiment": "neutral",
                    "action_items": [
                      "Call Sam Jones back on +447700900321 within the hour to plan a visit."
                    ],
                    "next_step": "Call back within the hour."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "balance.low": {
      "post": {
        "operationId": "balanceLow",
        "tags": [
          "Webhooks"
        ],
        "summary": "The balance dropped below the top-up threshold",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BalanceLowEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T11:00:01.000Z",
                "type": "balance.low",
                "data": {
                  "balance_eur": 4.88,
                  "threshold_eur": 5
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "balance.topped_up": {
      "post": {
        "operationId": "balanceToppedUp",
        "tags": [
          "Webhooks"
        ],
        "summary": "A top-up was added to the balance",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BalanceToppedUpEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T11:00:01.000Z",
                "type": "balance.topped_up",
                "data": {
                  "balance_eur": 24.76,
                  "transaction": {
                    "id": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09",
                    "type": "top_up",
                    "source": "payment",
                    "amount_eur": 20,
                    "balance_after_eur": 24.76,
                    "description": "Top-up",
                    "quantity": null,
                    "unit": null,
                    "reference_id": "b7e5a1c4-2d93-4f60-8a1e-9c3d5b7f2e10",
                    "created_at": "2026-10-01T09:30:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "balance.reload_failed": {
      "post": {
        "operationId": "balanceReloadFailed",
        "tags": [
          "Webhooks"
        ],
        "summary": "An automatic top-up failed",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BalanceReloadFailedEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T11:00:01.000Z",
                "type": "balance.reload_failed",
                "data": {
                  "balance_eur": 4.88,
                  "threshold_eur": 5,
                  "reload": {
                    "id": "d2f4a6b8-1c3e-4a5b-9d7f-0e1a3c5b7d92",
                    "amount_eur": 20,
                    "status": "failed",
                    "failure_reason": "insufficient_funds",
                    "created_at": "2026-10-01T11:00:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "document.created": {
      "post": {
        "operationId": "documentCreated",
        "tags": [
          "Webhooks"
        ],
        "summary": "A document was added to a collection",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentCreatedEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:30:01.000Z",
                "type": "document.created",
                "data": {
                  "collection": {
                    "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
                    "name": "contact"
                  },
                  "document": {
                    "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
                    "data": {
                      "name": "Anna de Vries",
                      "email": "anna@example.com",
                      "message": "Can you come and look at a leaking tap on Friday?"
                    },
                    "source": "public",
                    "created_at": "2026-10-01T09:30:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "email.sent": {
      "post": {
        "operationId": "emailSent",
        "tags": [
          "Webhooks"
        ],
        "summary": "An email was accepted for delivery (one event per recipient)",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailSentEvent"
              },
              "example": {
                "id": "evt_3b2f6c1d9e8a4f7b8c6d5e4f3a2b1c0d",
                "type": "email.sent",
                "created_at": "2026-10-15T09:30:05.000Z",
                "data": {
                  "email_id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "recipient": "sam@example.org",
                  "recipient_field": "to",
                  "status": "sent",
                  "reason": null,
                  "occurred_at": "2026-10-15T09:30:04.000Z",
                  "test": false
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "email.delivered": {
      "post": {
        "operationId": "emailDelivered",
        "tags": [
          "Webhooks"
        ],
        "summary": "An email reached the recipient's mail server",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailDeliveredEvent"
              },
              "example": {
                "id": "evt_3b2f6c1d9e8a4f7b8c6d5e4f3a2b1c0d",
                "type": "email.delivered",
                "created_at": "2026-10-15T09:30:05.000Z",
                "data": {
                  "email_id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "recipient": "sam@example.org",
                  "recipient_field": "to",
                  "status": "delivered",
                  "reason": null,
                  "occurred_at": "2026-10-15T09:30:04.000Z",
                  "test": false
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "email.delayed": {
      "post": {
        "operationId": "emailDelayed",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delivery of an email is delayed; it is retried automatically",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailDelayedEvent"
              },
              "example": {
                "id": "evt_3b2f6c1d9e8a4f7b8c6d5e4f3a2b1c0d",
                "type": "email.delayed",
                "created_at": "2026-10-15T09:30:05.000Z",
                "data": {
                  "email_id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "recipient": "sam@example.org",
                  "recipient_field": "to",
                  "status": "sent",
                  "reason": "Delayed: the recipient's mail server has not accepted it yet. Delivery is retried automatically.",
                  "occurred_at": "2026-10-15T09:30:04.000Z",
                  "test": false
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "email.bounced": {
      "post": {
        "operationId": "emailBounced",
        "tags": [
          "Webhooks"
        ],
        "summary": "An email bounced",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailBouncedEvent"
              },
              "example": {
                "id": "evt_3b2f6c1d9e8a4f7b8c6d5e4f3a2b1c0d",
                "type": "email.bounced",
                "created_at": "2026-10-15T09:30:05.000Z",
                "data": {
                  "email_id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "recipient": "sam@example.org",
                  "recipient_field": "to",
                  "status": "bounced",
                  "reason": "The address does not exist or does not accept email.",
                  "occurred_at": "2026-10-15T09:30:04.000Z",
                  "test": false
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "email.complained": {
      "post": {
        "operationId": "emailComplained",
        "tags": [
          "Webhooks"
        ],
        "summary": "The recipient marked an email as spam",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailComplainedEvent"
              },
              "example": {
                "id": "evt_3b2f6c1d9e8a4f7b8c6d5e4f3a2b1c0d",
                "type": "email.complained",
                "created_at": "2026-10-15T09:30:05.000Z",
                "data": {
                  "email_id": "7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
                  "app_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                  "recipient": "sam@example.org",
                  "recipient_field": "to",
                  "status": "complained",
                  "reason": "The recipient marked it as spam.",
                  "occurred_at": "2026-10-15T09:30:04.000Z",
                  "test": false
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    },
    "webhook.test": {
      "post": {
        "operationId": "webhookTest",
        "tags": [
          "Webhooks"
        ],
        "summary": "A test event from POST /webhooks/{id}/test",
        "description": "We POST this event to every active endpoint subscribed to it (webhook.test only to the endpoint you test). Respond with any 2xx within ten seconds; anything else is retried with backoff for up to 24 hours, in eight attempts. The event id stays the same on retries, so you can skip duplicates.",
        "parameters": [
          {
            "name": "Hypamoon-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of \"<t>.<raw body>\" with the full secret (including whsec_) as the key. Check it on the raw body before you parse the JSON, and reject a t more than five minutes from your clock.",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookTestEvent"
              },
              "example": {
                "id": "evt_4f1c2a9b8e7d6c5b4a39281706f5e4d3",
                "created_at": "2026-10-01T09:15:38.000Z",
                "type": "webhook.test",
                "data": {
                  "endpoint_id": "9e8d7c6b-5a49-4382-9716-a5b4c3d2e1f0"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received. Any 2xx counts."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "McpRequest": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "properties": {}
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ]
      },
      "McpResponse": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "anyOf": [
              {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "number"
                  }
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "properties": {}
          }
        },
        "required": [
          "jsonrpc",
          "id"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the project."
          },
          "name": {
            "type": "string",
            "description": "The name of the project. The assistant uses it in its greeting."
          },
          "edition": {
            "type": "string",
            "description": "Always api for a project of the Hypamoon API."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the project was created."
          },
          "spending_limit_eur": {
            "type": [
              "number",
              "null"
            ],
            "description": "The most the project can spend per month, in euros. Null in test mode: a test project costs nothing. Left out for a key or connection limited to some sites and apps."
          },
          "numbers_count": {
            "type": "integer",
            "description": "How many numbers the project has. Left out for a limited key or connection."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "live or test: the mode of the key, which is always the mode of its project."
          },
          "live_project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Only in test mode: the live project this test project belongs to, or the project itself when it has no separate test project."
          },
          "rails": {
            "type": "object",
            "properties": {
              "phone": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "Numbers, the call plan and the AI receptionist on inbound calls."
              },
              "outbound": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "Outbound calls (start_call)."
              },
              "sms": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "SMS messages."
              },
              "email": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "Email to other people. With a live key, email to the project's own team is real even when this is test."
              },
              "payments": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "Checkout, orders and refunds: real money or test payments."
              },
              "domains": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "description": "Buying, renewing and transferring a domain. Connecting a domain is always real."
              }
            },
            "required": [
              "phone",
              "outbound",
              "sms",
              "email",
              "payments",
              "domains"
            ],
            "additionalProperties": false,
            "description": "Per function with real-world effects, for this request: live (in use) or test (simulated: not used yet, or a test key). A function on test becomes live the first time it is asked for, once the project is verified for it; until then the request answers verification_required with a link. A test key is test everywhere. Left out for a limited key or connection. Sites, data and apps are always real and have no mode."
          },
          "verification": {
            "type": "object",
            "properties": {
              "account_type": {
                "type": "string",
                "enum": [
                  "personal",
                  "business"
                ],
                "description": "personal or business. You are verified for what you use: a personal account needs no business register number, a business does. A national number needs a business."
              },
              "levels": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "level": {
                      "type": "string",
                      "enum": [
                        "test",
                        "company",
                        "personal",
                        "number_documents",
                        "full"
                      ],
                      "description": "test, then company (a business) or personal (a personal account), then number_documents and full. Each level unlocks more; see unlocks."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "not_started",
                        "submitted",
                        "in_review",
                        "approved",
                        "rejected"
                      ],
                      "description": "Where this level stands. company, personal and number_documents follow from the data you set; full needs our review."
                    },
                    "reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Only when rejected: what is wrong. Fix it and submit again."
                    },
                    "missing": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "What is still needed, as codes: company_details.<field> (set with PUT /project/company), payment_mandate, spending_cap, document.proof_of_address, document.identity, document.company_registration (upload in the portal under Verification), level.company or level.personal (finish that level first), level.number_documents, director_identity, ubo_confirmation and review. Empty when approved."
                    },
                    "unlocks": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "What this level unlocks, as codes: test_keys, test_numbers, simulated_calls, live_keys, live_chat, national_numbers, inbound_calls, geographic_numbers, sms, outbound_calls, spending_cap_above_500_eur, more_concurrent_calls."
                    }
                  },
                  "required": [
                    "level",
                    "status",
                    "reason",
                    "missing",
                    "unlocks"
                  ],
                  "additionalProperties": false
                },
                "description": "Always four levels, in order: test, company (personal for a personal account), number_documents, full."
              }
            },
            "required": [
              "account_type",
              "levels"
            ],
            "additionalProperties": false,
            "description": "The verification (KYC) of the project. In test mode: the verification of the live project."
          },
          "access": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "limited_by": {
                "type": "string",
                "enum": [
                  "api_key",
                  "connection",
                  "member"
                ],
                "description": "api_key or connection: an owner or admin limited this key or chat connection in the portal. member: the person behind it is limited in the team."
              },
              "sites": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The id."
                    },
                    "name": {
                      "type": "string",
                      "description": "The name."
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ],
                  "additionalProperties": false
                },
                "description": "The sites it can use. The app of a site comes with it."
              },
              "apps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The id."
                    },
                    "name": {
                      "type": "string",
                      "description": "The name."
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ],
                  "additionalProperties": false
                },
                "description": "Apps chosen on their own, next to the apps of the sites."
              },
              "collections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The id."
                    },
                    "name": {
                      "type": "string",
                      "description": "The name."
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ],
                  "additionalProperties": false
                },
                "description": "The collections it can use: the ones the sites and apps use."
              }
            },
            "required": [
              "limited_by",
              "sites",
              "apps",
              "collections"
            ],
            "additionalProperties": false,
            "description": "Null: this key or connection can use the whole project. Otherwise only these sites, apps and collections: lists show only those, and anything for the whole project (billing, team, domains, numbers, a new site or app) is refused with 403 and a user_message."
          }
        },
        "required": [
          "id",
          "name",
          "edition",
          "created_at",
          "mode",
          "access"
        ],
        "additionalProperties": false,
        "description": "The project behind the API key."
      },
      "Advice": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "webshop",
              "bookings",
              "restaurant",
              "course_membership",
              "wordpress_portal"
            ]
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": "string",
            "enum": [
              "eu",
              "uk",
              "us",
              "other"
            ],
            "description": "eu (detailed), uk and us (basics), other (a general fallback)."
          },
          "audience": {
            "type": "string",
            "enum": [
              "consumers",
              "businesses",
              "both"
            ]
          },
          "how_to_use": {
            "type": "string",
            "description": "For the agent: inform yourself, ask only the blocking questions, use the defaults for the rest, and offer the checklist; never a questionnaire or a gate."
          },
          "ask_first": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "question": {
                  "type": "string"
                },
                "why": {
                  "type": "string"
                },
                "default": {
                  "type": "string",
                  "description": "What to use when the user does not know or skips the question."
                },
                "blocking": {
                  "type": "boolean",
                  "description": "Only these block a working result; at most two or three per type."
                }
              },
              "required": [
                "id",
                "question",
                "why",
                "default",
                "blocking"
              ]
            }
          },
          "build_plan": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "how": {
                  "type": "string"
                },
                "tools": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "optional": {
                  "type": "boolean"
                }
              },
              "required": [
                "id",
                "title",
                "how",
                "tools"
              ]
            }
          },
          "go_live_checklist": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "level": {
                  "type": "string",
                  "enum": [
                    "usually_required",
                    "recommended"
                  ]
                },
                "description": {
                  "type": "string"
                },
                "why": {
                  "type": "string"
                },
                "how_on_hypamoon": {
                  "type": "string"
                },
                "checked_by_check_readiness": {
                  "type": "boolean"
                }
              },
              "required": [
                "id",
                "level",
                "description",
                "why",
                "how_on_hypamoon",
                "checked_by_check_readiness"
              ]
            }
          },
          "pitfalls": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "pitfall": {
                  "type": "string"
                },
                "instead": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "pitfall",
                "instead"
              ]
            }
          },
          "structured_data": {
            "type": "object",
            "properties": {
              "schema_type": {
                "type": "string",
                "description": "The schema.org type, like LocalBusiness, Restaurant, Product or Course."
              },
              "why": {
                "type": "string"
              },
              "snippet": {
                "type": "string",
                "description": "A <script type=\"application/ld+json\"> block with YOUR ... placeholders; fill them with the user's real details only, never invented ones."
              },
              "required": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The fields check_readiness expects in the block."
              }
            },
            "required": [
              "schema_type",
              "why",
              "snippet",
              "required"
            ],
            "description": "Structured data for search engines, ready to fill in and add with deploy_site."
          },
          "note": {
            "type": "string",
            "description": "Guidance, not legal or tax advice."
          },
          "other_types": {
            "type": "object",
            "properties": {
              "available": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "webshop",
                    "bookings",
                    "restaurant",
                    "course_membership",
                    "wordpress_portal"
                  ]
                }
              },
              "planned": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "available",
              "planned"
            ]
          },
          "known_problems": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "sandbox_blocks_upload",
                    "image_too_large",
                    "version_conflict",
                    "phone_verification_required",
                    "site_not_visible",
                    "is_hypamoon_down"
                  ]
                },
                "symptom": {
                  "type": "string",
                  "description": "How you recognise it."
                },
                "what_to_do": {
                  "type": "string",
                  "description": "What you, the agent, do."
                },
                "tell_user": {
                  "type": "string",
                  "description": "What you can say to the user, in plain words."
                }
              },
              "required": [
                "id",
                "symptom",
                "what_to_do",
                "tell_user"
              ]
            },
            "description": "Known problems (an upload blocked by a sandbox, an image too large, version_conflict, phone verification, a site nobody else can see, is Hypamoon down), each with what to do and what to tell the user. GET /advice?topic=<id> returns one."
          },
          "summary_markdown": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "summary",
          "country",
          "region",
          "audience",
          "how_to_use",
          "ask_first",
          "build_plan",
          "go_live_checklist",
          "pitfalls",
          "structured_data",
          "note",
          "other_types",
          "known_problems",
          "summary_markdown"
        ]
      },
      "Status": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "down",
              "unknown"
            ],
            "description": "The overall status: the worst of the components (unknown does not count)."
          },
          "source": {
            "type": "string",
            "enum": [
              "status_page",
              "self_check"
            ],
            "description": "status_page: from status.hypamoon.com. self_check: that page did not answer, so only a minimal check of our own."
          },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "operational",
                    "degraded",
                    "down",
                    "unknown"
                  ]
                }
              },
              "required": [
                "id",
                "name",
                "status"
              ],
              "additionalProperties": false
            },
            "description": "Per component: sites, api, portal, phone, ai, mail, payments."
          },
          "incidents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "component": {
                  "type": "string"
                },
                "severity": {
                  "type": "string"
                },
                "started_at": {
                  "type": "string"
                },
                "latest_update": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "component",
                "severity",
                "started_at",
                "latest_update"
              ],
              "additionalProperties": false
            },
            "description": "Reported problems that are not resolved yet."
          },
          "status_page": {
            "type": "string",
            "description": "The public status page."
          },
          "checked_at": {
            "type": "string",
            "description": "When the status was measured."
          },
          "user_message": {
            "type": "string",
            "description": "One calm sentence for the user, like \"Hypamoon is working normally. The problem is likely in your chat app.\""
          },
          "next_step": {
            "type": "string",
            "description": "What the chat or agent should do next."
          }
        },
        "required": [
          "status",
          "source",
          "components",
          "incidents",
          "status_page",
          "checked_at",
          "user_message",
          "next_step"
        ],
        "additionalProperties": false,
        "description": "Whether Hypamoon is working, per component."
      },
      "MoveToLiveInput": {
        "type": "object",
        "properties": {
          "sites": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "maxItems": 100,
            "description": "Optional. The sites to move, by id or slug. Leave it out to move every site of the test project."
          },
          "collections": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "maxItems": 100,
            "description": "Optional. The collections to move, by name or id. Leave it out to move the collections the chosen sites use (a site uses a collection when its files name it, like a form from get_form_snippet)."
          },
          "shared_collections": {
            "type": "string",
            "enum": [
              "refuse",
              "copy"
            ],
            "description": "What to do with a collection that a site staying in test also uses. refuse (default): nothing is moved and the answer lists them. copy: the live project gets its own copy with all documents; the test site keeps the original."
          }
        },
        "additionalProperties": false,
        "description": "Move sites from the test project to the live project."
      },
      "MoveToLiveUndoInput": {
        "type": "object",
        "properties": {
          "move_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. The id from move_to_live. Leave it out to undo the latest move of this project."
          }
        },
        "additionalProperties": false,
        "description": "Undo a move to live within 24 hours."
      },
      "MoveToLive": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of this move, for undo_move_to_live."
          },
          "status": {
            "type": "string",
            "enum": [
              "done",
              "partly_done"
            ],
            "description": "partly_done: some steps after the move did not finish; see errors. Support is notified."
          },
          "from_project": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "test",
                  "live"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "mode"
            ]
          },
          "to_project": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "test",
                  "live"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "mode"
            ]
          },
          "moved": {
            "type": "object",
            "properties": {
              "sites": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "new_version": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The new version with the live form token, labelled \"moved to live\"; null when no file had a test token."
                    },
                    "form_token_replaced": {
                      "type": "boolean"
                    },
                    "waiting_for_approval": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "id",
                    "slug",
                    "url",
                    "new_version",
                    "form_token_replaced"
                  ]
                }
              },
              "collections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "documents": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "documents"
                  ]
                }
              },
              "copied_collections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "test_id": {
                      "type": "string"
                    },
                    "live_id": {
                      "type": "string"
                    },
                    "documents": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "name",
                    "test_id",
                    "live_id",
                    "documents"
                  ]
                }
              },
              "apps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "site_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "site_id"
                  ]
                }
              },
              "agents": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "const": "paused"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "status"
                  ]
                }
              },
              "domains": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "form_tokens": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "live_token_id": {
                      "type": "string"
                    },
                    "replaces_test_token_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "name",
                    "live_token_id",
                    "replaces_test_token_id"
                  ]
                }
              },
              "webhooks": {
                "type": "number"
              }
            },
            "required": [
              "sites",
              "collections",
              "copied_collections",
              "apps",
              "agents",
              "domains",
              "form_tokens",
              "webhooks"
            ]
          },
          "stayed_in_test": {
            "type": "object",
            "properties": {
              "sites": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "slug"
                  ]
                }
              },
              "collections": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "simulated_domains": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "sites",
              "collections",
              "simulated_domains"
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "undo": {
            "type": "object",
            "properties": {
              "until": {
                "type": "string"
              },
              "how": {
                "type": "string"
              }
            },
            "required": [
              "until",
              "how"
            ]
          },
          "connection": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "test",
                  "live"
                ]
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "mode",
              "note"
            ],
            "description": "For a chat connected through OAuth: which project the connection points to now."
          },
          "summary": {
            "type": "string"
          },
          "next_step": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "from_project",
          "to_project",
          "moved",
          "stayed_in_test",
          "warnings",
          "errors",
          "undo",
          "connection",
          "summary",
          "next_step"
        ]
      },
      "MoveToLiveUndo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "undone",
              "partly_undone"
            ]
          },
          "sites": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "collections": {
            "type": "number"
          },
          "apps": {
            "type": "number"
          },
          "agents": {
            "type": "number"
          },
          "domains": {
            "type": "number"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "summary": {
            "type": "string"
          },
          "next_step": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "sites",
          "collections",
          "apps",
          "agents",
          "domains",
          "errors",
          "summary",
          "next_step"
        ]
      },
      "SiteReadiness": {
        "type": "object",
        "properties": {
          "site": {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "url": {
                "type": "string"
              },
              "live": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "url",
              "live"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "webshop",
              "bookings",
              "restaurant",
              "course_membership",
              "wordpress_portal",
              null
            ]
          },
          "type_source": {
            "type": "string",
            "enum": [
              "given",
              "detected",
              "none"
            ]
          },
          "region": {
            "type": "string",
            "enum": [
              "eu",
              "uk",
              "us",
              "other"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "works": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "terms_page",
                    "privacy_page",
                    "returns_page",
                    "contact_details",
                    "forms_saved",
                    "checkout_real",
                    "prices_currency",
                    "no_placeholders",
                    "no_test_wording",
                    "internal_links",
                    "not_found_page",
                    "no_fake_login",
                    "no_local_storage_db",
                    "seo_title",
                    "seo_description",
                    "seo_h1",
                    "seo_lang",
                    "seo_open_graph",
                    "seo_image_alt",
                    "seo_image_size",
                    "seo_viewport",
                    "seo_favicon",
                    "structured_data",
                    "app_data_per_user",
                    "app_backup_tables"
                  ]
                },
                "title": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "title",
                "detail"
              ]
            }
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "terms_page",
                    "privacy_page",
                    "returns_page",
                    "contact_details",
                    "forms_saved",
                    "checkout_real",
                    "prices_currency",
                    "no_placeholders",
                    "no_test_wording",
                    "internal_links",
                    "not_found_page",
                    "no_fake_login",
                    "no_local_storage_db",
                    "seo_title",
                    "seo_description",
                    "seo_h1",
                    "seo_lang",
                    "seo_open_graph",
                    "seo_image_alt",
                    "seo_image_size",
                    "seo_viewport",
                    "seo_favicon",
                    "structured_data",
                    "app_data_per_user",
                    "app_backup_tables"
                  ]
                },
                "title": {
                  "type": "string"
                },
                "explanation": {
                  "type": "string",
                  "description": "In plain words, as advice: before you go live, you might want to."
                },
                "next_step": {
                  "type": "object",
                  "properties": {
                    "tool": {
                      "type": "string"
                    },
                    "how": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "tool",
                    "how"
                  ]
                },
                "paths": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": [
                "id",
                "title",
                "explanation",
                "next_step"
              ]
            }
          },
          "check_with_owner": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "level": {
                  "type": "string",
                  "enum": [
                    "usually_required",
                    "recommended"
                  ]
                },
                "description": {
                  "type": "string"
                },
                "why": {
                  "type": "string"
                },
                "how_on_hypamoon": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "level",
                "description",
                "why",
                "how_on_hypamoon"
              ]
            },
            "description": "What cannot be checked on the site; ask the owner, never require an answer."
          },
          "pages_checked": {
            "type": "integer"
          },
          "note": {
            "type": "string"
          },
          "summary_markdown": {
            "type": "string"
          }
        },
        "required": [
          "site",
          "type",
          "type_source",
          "region",
          "country",
          "works",
          "missing",
          "check_with_owner",
          "pages_checked",
          "note",
          "summary_markdown"
        ]
      },
      "SearchConsoleVerifyInput": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "The site to verify. For method meta this is the site that gets the tag; for dns the domain connected to this site is used."
          },
          "domain": {
            "type": "string",
            "minLength": 3,
            "maxLength": 253,
            "description": "A domain connected in this project (connect_domain), like example.com. For method dns; for meta, the site on this domain."
          },
          "method": {
            "type": "string",
            "enum": [
              "dns",
              "meta"
            ],
            "description": "dns: a TXT record on the connected domain (a domain property in Search Console). meta: a meta tag on the home page (property type URL prefix)."
          },
          "token": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500,
            "description": "The verification token from Search Console: the value alone, google-site-verification=..., or the whole meta tag."
          }
        },
        "required": [
          "method",
          "token"
        ],
        "additionalProperties": false
      },
      "SearchConsoleVerification": {
        "type": "object",
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "dns",
              "meta"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "added",
              "already_present",
              "waiting_for_approval"
            ],
            "description": "added: done now. already_present: it was there already. waiting_for_approval: the project needs a second person to approve the new version first."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "property": {
            "type": "string",
            "description": "What to enter in Search Console: sc-domain:<domain> for a domain property, or the address for a URL prefix property."
          },
          "record": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "TXT"
              },
              "name": {
                "type": "string"
              },
              "content": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "name",
              "content"
            ]
          },
          "version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The new site version with the tag (meta), when versions are on."
          },
          "sitemap": {
            "type": "string",
            "description": "The sitemap to submit in Search Console under Sitemaps; the site serves it automatically."
          },
          "next_steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "method",
          "status",
          "site_id",
          "domain",
          "property",
          "sitemap",
          "next_steps"
        ]
      },
      "SiteStats": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "from": {
            "type": "string",
            "description": "The first day (UTC) in the range."
          },
          "to": {
            "type": "string",
            "description": "The last day (UTC), today."
          },
          "totals": {
            "type": "object",
            "properties": {
              "page_views": {
                "type": "integer",
                "description": "Page views by people: navigations to a page, without images, scripts and bots."
              },
              "bot_page_views_excluded": {
                "type": "integer",
                "description": "Page views by crawlers and scripts, left out of the numbers above."
              }
            },
            "required": [
              "page_views",
              "bot_page_views_excluded"
            ]
          },
          "per_day": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "page_views": {
                  "type": "integer"
                }
              },
              "required": [
                "date",
                "page_views"
              ]
            }
          },
          "top_pages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "page_views": {
                  "type": "integer"
                }
              },
              "required": [
                "path",
                "page_views"
              ]
            },
            "description": "Up to 10 paths with the most page views. Paths are recorded since 9 October 2026."
          },
          "origins": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "origin": {
                  "type": "string",
                  "enum": [
                    "direct",
                    "internal",
                    "search",
                    "social",
                    "email",
                    "shortener",
                    "external"
                  ]
                },
                "page_views": {
                  "type": "integer"
                }
              },
              "required": [
                "origin",
                "page_views"
              ]
            },
            "description": "Where page views came from: direct (no referrer), internal (from the site itself), search, social, email, shortener or external (another site)."
          },
          "note": {
            "type": "string"
          }
        },
        "required": [
          "site_id",
          "slug",
          "days",
          "from",
          "to",
          "totals",
          "per_day",
          "top_pages",
          "origins",
          "note"
        ]
      },
      "SitePage": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "The address that was asked for."
          },
          "final_url": {
            "type": "string",
            "description": "The address that answered, after the redirects that were followed."
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "HEAD"
            ]
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status of the final answer."
          },
          "ok": {
            "type": "boolean",
            "description": "true for a 2xx status."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "content-type, cache-control, location, etag, x-* and a few more. Never set-cookie."
          },
          "redirects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "integer"
                },
                "url": {
                  "type": "string"
                },
                "location": {
                  "type": "string"
                }
              },
              "required": [
                "status",
                "url",
                "location"
              ]
            },
            "description": "The redirects that were followed, in order."
          },
          "redirect_blocked": {
            "type": "string",
            "description": "Set when a redirect was not followed (another host, not https, or too many); status and headers are then of that redirect."
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "The body as text, at most 20 KB; null for HEAD, an empty body or a body that is not text."
          },
          "body_bytes": {
            "type": "integer",
            "description": "Bytes received (reading stops after 256 KB)."
          },
          "truncated": {
            "type": "boolean",
            "description": "true when the body is longer than what is returned."
          },
          "body_note": {
            "type": "string",
            "description": "Why the body is not shown, when it is not."
          },
          "elapsed_ms": {
            "type": "integer"
          }
        },
        "required": [
          "url",
          "final_url",
          "method",
          "status",
          "ok",
          "headers",
          "redirects",
          "content_type",
          "body",
          "body_bytes",
          "truncated",
          "elapsed_ms"
        ]
      },
      "CompanyInput": {
        "type": "object",
        "properties": {
          "account_type": {
            "type": "string",
            "enum": [
              "personal",
              "business"
            ],
            "default": "business",
            "description": "personal (someone without a company: no company_name, kvk_number, registration_number or vat_number) or business. Leave it out to keep the project's current type. A national number needs business. When a personal account is not available for the address, validation_failed says so."
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "The legal name. Required for a business, not allowed for a personal account."
          },
          "kvk_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "For a company in the Netherlands (required there): the KvK number, 8 digits. Spaces and dots are removed."
          },
          "registration_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40,
            "description": "For a company outside the Netherlands: the number in its country's business register (like a UK Companies House number or a German HRB number). Needed for the company verification level outside the EU."
          },
          "vat_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The VAT number. In the EU outside the Netherlands it is needed for the company verification level and checked with VIES: send it with or without the country prefix. A number that VIES rejects returns vat_number_invalid."
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200,
                "description": "The street, with the house number if the address has no separate one."
              },
              "house_number": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 20,
                "description": "The house number, starting with a digit, like 40 or 12A. Required where the country's addresses use one (like the Netherlands), optional elsewhere."
              },
              "postal_code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 16,
                "description": "The postal code. It is normalized for the country where we know the format (in the Netherlands 1000aa is saved as 1000 AA)."
              },
              "city": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100,
                "description": "The city."
              },
              "country": {
                "type": "string",
                "default": "NL",
                "description": "ISO 3166-1 alpha-2, like GB, DE or NL. Defaults to NL when left out. A country we cannot serve returns country_not_supported."
              }
            },
            "required": [
              "street",
              "postal_code",
              "city"
            ],
            "additionalProperties": false
          },
          "contact": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200,
                "description": "The contact person's first and last name. For a business it can also be a role, like Administration or Webmaster; this name also goes on the domains registered or transferred through Hypamoon, unless registrar_contact_name is set. Ordering a phone number still needs a person with a first and last name. For a personal account: the user's own first and last name."
              },
              "phone": {
                "type": "string",
                "description": "A phone number where the contact can be reached, in E.164."
              }
            },
            "required": [
              "name",
              "phone"
            ],
            "additionalProperties": false,
            "description": "A person the regulator can reach today. For a personal account: the user."
          },
          "registrar_contact_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Optional, for a business: the name on the contacts of the domains registered or transferred through Hypamoon, when it should differ from contact.name. Use a role, like Administration or Webmaster, so the domain registration carries no personal name while contact.name stays the person that number registration needs. Leave it out to keep the saved value; send null to use contact.name again. Not allowed for a personal account."
          }
        },
        "required": [
          "address",
          "contact"
        ],
        "additionalProperties": false,
        "description": "Who the project belongs to, and who its phone numbers are registered to: a business or a personal account (account_type). For a business: company_name, the address, the number in its country's business register (kvk_number in the Netherlands, registration_number elsewhere) and, in the EU, vat_number (checked with VIES). For a personal account: the user's address and the contact (the user), without company fields."
      },
      "Company": {
        "type": "object",
        "properties": {
          "account_type": {
            "type": "string",
            "enum": [
              "personal",
              "business"
            ],
            "description": "personal (your own details, no business register number) or business."
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The legal company name; null for a personal account."
          },
          "kvk_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "8 digits, for a company in the Netherlands; null elsewhere."
          },
          "registration_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number in the business register of the company's country, for a company outside the Netherlands (there it is kvk_number)."
          },
          "vat_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The VAT number, with country prefix in the EU (like DE123456789)."
          },
          "vat_status": {
            "type": "string",
            "enum": [
              "valid",
              "invalid",
              "unverified",
              "missing",
              "not_required"
            ],
            "description": "For a company in the EU: the VIES check of vat_number (unverified when VIES did not answer; we retry automatically). missing: no VAT number yet. not_required: no VIES check needed for this country."
          },
          "vat_checked_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When VIES last checked vat_number, ISO 8601."
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string"
              },
              "house_number": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postal_code": {
                "type": "string",
                "description": "Normalized where we know the country's format."
              },
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2, like NL, DE or GB."
              }
            },
            "required": [
              "street",
              "house_number",
              "postal_code",
              "city",
              "country"
            ],
            "additionalProperties": false
          },
          "contact": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "description": "In E.164."
              }
            },
            "required": [
              "name",
              "phone"
            ],
            "additionalProperties": false
          },
          "registrar_contact_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only for a business: the name on the domain contacts at the registry when it differs from contact.name, or null (then contact.name is used)."
          },
          "complete": {
            "type": "boolean",
            "description": "The details are complete enough for the company or personal verification level: a business needs its business register number and, in the EU, a valid VAT number; a personal account needs a complete address."
          },
          "domain_contacts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "domain": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "updated",
                    "unchanged",
                    "skipped",
                    "failed"
                  ],
                  "description": "updated: the registry has the new contact, email, phone and address. unchanged: it already had them. skipped: not sent, see reason (a test domain, or a change of holder, which only Hypamoon support can do). failed: the registry or registrar refused it, see reason."
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "domain",
                "status",
                "reason"
              ],
              "additionalProperties": false
            },
            "description": "Only when the project has domains registered or transferred through Hypamoon: per domain, whether the new details reached the domain's contact at the registry. A change of organization, registration number or (for a personal account) person is never sent: that is a change of holder."
          }
        },
        "required": [
          "account_type",
          "company_name",
          "kvk_number",
          "registration_number",
          "vat_number",
          "vat_status",
          "vat_checked_at",
          "address",
          "contact",
          "complete"
        ],
        "additionalProperties": false,
        "description": "The company details of the project (or, for a personal account, your own details), as saved."
      },
      "Number": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the number. Use it for the call plan."
          },
          "e164": {
            "type": "string",
            "description": "The number in E.164, like +447700900123."
          },
          "type": {
            "type": "string",
            "description": "national (non-geographic), geographic (with an area code), mobile or tollfree."
          },
          "status": {
            "type": "string",
            "description": "pending (the registration is with the carrier, usually active within one business day), active or porting."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the number was added to the project."
          },
          "test": {
            "type": "boolean",
            "description": "true for a test number (test mode), from the range +447700900000 to +447700900999. It can never be called for real."
          }
        },
        "required": [
          "id",
          "e164",
          "type",
          "status",
          "created_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "A phone number of the project."
      },
      "OrderedNumber": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the number. Use it for the call plan."
          },
          "e164": {
            "type": "string",
            "description": "The number in E.164, like +447700900123."
          },
          "type": {
            "type": "string",
            "description": "national (non-geographic), geographic (with an area code), mobile or tollfree."
          },
          "status": {
            "type": "string",
            "description": "pending (the registration is with the carrier, usually active within one business day), active or porting."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the number was added to the project."
          },
          "test": {
            "type": "boolean",
            "description": "true for a test number (test mode), from the range +447700900000 to +447700900999. It can never be called for real."
          },
          "from_stock": {
            "type": "boolean",
            "description": "Whether the number came from our stock. A number from stock works right away."
          }
        },
        "required": [
          "id",
          "e164",
          "type",
          "status",
          "created_at",
          "test",
          "from_stock"
        ],
        "additionalProperties": false,
        "description": "The number you ordered."
      },
      "NumberList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Number"
            }
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "ReleasedNumber": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the number that was released."
          },
          "e164": {
            "type": "string",
            "description": "The number in E.164, like +447700900123."
          },
          "released": {
            "type": "boolean",
            "const": true,
            "description": "Always true: the number is no longer in the project."
          },
          "test": {
            "type": "boolean",
            "const": true,
            "description": "Always true: only test numbers can be released through the API."
          }
        },
        "required": [
          "id",
          "e164",
          "released",
          "test"
        ],
        "additionalProperties": false,
        "description": "A test number that was released."
      },
      "AvailableNumber": {
        "type": "object",
        "properties": {
          "e164": {
            "type": "string",
            "description": "The number in E.164, like +447700900123."
          },
          "type": {
            "type": "string",
            "enum": [
              "national",
              "geographic"
            ]
          },
          "from_stock": {
            "type": "boolean",
            "description": "Whether the number is in our stock and works right away."
          },
          "test": {
            "type": "boolean",
            "description": "true for a test number (test mode)."
          }
        },
        "required": [
          "e164",
          "type",
          "from_stock",
          "test"
        ],
        "additionalProperties": false,
        "description": "A number you can order with POST /numbers."
      },
      "AvailableNumberList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailableNumber"
            }
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "NumberOrder": {
        "type": "object",
        "properties": {
          "phone_number": {
            "type": "string",
            "description": "A number from GET /numbers/available, in E.164. With a test key: a test number from that same search."
          },
          "area": {
            "type": "string",
            "description": "Instead of phone_number: we pick the first free number with this area code, as GET /numbers/available returns it."
          }
        },
        "additionalProperties": false,
        "description": "Send either phone_number or area, not both.",
        "oneOf": [
          {
            "required": [
              "phone_number"
            ]
          },
          {
            "required": [
              "area"
            ]
          }
        ]
      },
      "AssistantInput": {
        "type": "object",
        "properties": {
          "voice": {
            "type": "string",
            "enum": [
              "saar",
              "fenna",
              "merel",
              "julia",
              "roos",
              "tim",
              "bram",
              "daan",
              "ruben",
              "ava",
              "nora",
              "liam",
              "owen",
              "lena",
              "mara",
              "jonas",
              "felix",
              "chloe",
              "manon",
              "hugo",
              "theo",
              "lucia",
              "elena",
              "pablo",
              "diego"
            ],
            "description": "The voice. It must speak the language: nl saar, fenna, merel, julia, roos, tim, bram, daan, ruben; en ava, nora, liam, owen; de lena, mara, jonas, felix; fr chloe, manon, hugo, theo; es lucia, elena, pablo, diego. Case does not matter. Default: the first voice of the language."
          },
          "language": {
            "type": "string",
            "enum": [
              "nl",
              "en",
              "de",
              "fr",
              "es"
            ],
            "description": "The language the assistant opens and speaks in: en (English), nl (Dutch), de (German), fr (French) or es (Spanish). The greeting follows it. Default: the language of the country of your first number (Dutch while the project has no number yet)."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "nl",
                "en",
                "de",
                "fr",
                "es"
              ]
            },
            "minItems": 1,
            "maxItems": 5,
            "description": "The languages your callers speak; language always comes first. One language is the most reliable, because speech recognition never has to guess. Two or more switch it to multi, which follows the caller sentence by sentence, and the assistant switches with them. auto and multi are not accepted. Default: [language]."
          },
          "instructions": {
            "type": "string",
            "maxLength": 4000,
            "description": "What the assistant does, as free text. Default empty."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional. We read the page and use it for opening hours, address and services. Must be a public web address."
          }
        },
        "additionalProperties": false,
        "description": "PUT replaces all fields: a field you leave out goes back to its default."
      },
      "Assistant": {
        "type": "object",
        "properties": {
          "configured": {
            "type": "boolean",
            "description": "Whether the assistant was ever set up with PUT /assistant."
          },
          "voice": {
            "type": [
              "string",
              "null"
            ],
            "description": "The voice, like ava. It speaks the language."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opens and speaks in, like en."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The languages your callers speak, with language first."
          },
          "speech_recognition": {
            "type": "string",
            "description": "The language of speech recognition: the one language, or multi when there are two or more."
          },
          "instructions": {
            "type": "string",
            "description": "What the assistant does: your instructions."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "The website the assistant reads for opening hours, address and services."
          },
          "greeting": {
            "type": "string",
            "description": "The fixed greeting, in the language. It follows from the project name and always says the caller is talking to a digital assistant."
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the assistant was last updated at the carrier."
          }
        },
        "required": [
          "configured",
          "voice",
          "language",
          "languages",
          "speech_recognition",
          "instructions",
          "website",
          "greeting",
          "synced_at"
        ],
        "additionalProperties": false,
        "description": "The assistant of the project."
      },
      "AssistantSaved": {
        "type": "object",
        "properties": {
          "configured": {
            "type": "boolean",
            "description": "Whether the assistant was ever set up with PUT /assistant."
          },
          "voice": {
            "type": [
              "string",
              "null"
            ],
            "description": "The voice, like ava. It speaks the language."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opens and speaks in, like en."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The languages your callers speak, with language first."
          },
          "speech_recognition": {
            "type": "string",
            "description": "The language of speech recognition: the one language, or multi when there are two or more."
          },
          "instructions": {
            "type": "string",
            "description": "What the assistant does: your instructions."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "The website the assistant reads for opening hours, address and services."
          },
          "greeting": {
            "type": "string",
            "description": "The fixed greeting, in the language. It follows from the project name and always says the caller is talking to a digital assistant."
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the assistant was last updated at the carrier."
          },
          "sync": {
            "type": "string",
            "enum": [
              "synced",
              "failed",
              "skipped_test_mode"
            ],
            "description": "synced when the assistant was updated right away; failed when not. Your settings are saved either way and applied with the next PUT. skipped_test_mode with a test key: a test project has no live assistant."
          },
          "call_plan": {
            "type": "string",
            "enum": [
              "added",
              "in_plan",
              "not_in_plan"
            ],
            "description": "added: the project only had the default plan (voicemail), so it now has a call plan where the assistant answers every call, with voicemail if the assistant is not available. in_plan: the call plan already sends calls to the assistant. not_in_plan: your own call plan does not use the assistant yet; add an ai_agent node with PUT /numbers/{id}/call-plan."
          },
          "next_step": {
            "type": "string",
            "description": "What to do next."
          },
          "user_message": {
            "type": "string",
            "description": "A sentence you can show the user as it is."
          }
        },
        "required": [
          "configured",
          "voice",
          "language",
          "languages",
          "speech_recognition",
          "instructions",
          "website",
          "greeting",
          "synced_at",
          "sync",
          "call_plan",
          "next_step",
          "user_message"
        ],
        "additionalProperties": false,
        "description": "The assistant as saved."
      },
      "CallPlan": {
        "type": "object",
        "properties": {
          "entry": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,64}$",
            "description": "The id of the first node."
          },
          "nodes": {
            "type": "object",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "announcement"
                    },
                    "text": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 1000,
                      "description": "The text to read out."
                    },
                    "next": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "The node to go to after the text."
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "text",
                    "next"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "hours"
                    },
                    "schedule": {
                      "type": "object",
                      "properties": {
                        "timezone": {
                          "type": "string"
                        },
                        "days": {
                          "type": "object",
                          "properties": {
                            "mon": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "tue": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "wed": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "thu": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "fri": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "sat": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            },
                            "sun": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "open": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  },
                                  "close": {
                                    "type": "string",
                                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                                  }
                                },
                                "required": [
                                  "open",
                                  "close"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 4
                            }
                          },
                          "additionalProperties": false
                        },
                        "closedDates": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                          },
                          "maxItems": 366
                        }
                      },
                      "required": [
                        "timezone",
                        "days"
                      ],
                      "additionalProperties": false,
                      "description": "Opening hours per day in a time zone, like Europe/London."
                    },
                    "open": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "The node to go to when open."
                    },
                    "closed": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "The node to go to when closed."
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "schedule",
                    "open",
                    "closed"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "forward"
                    },
                    "strategy": {
                      "type": "string",
                      "enum": [
                        "sequential",
                        "parallel"
                      ],
                      "description": "sequential rings the targets one after another, parallel rings them all at once."
                    },
                    "targets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "type": "string",
                            "const": "external"
                          },
                          "e164": {
                            "type": "string",
                            "pattern": "^\\+[1-9]\\d{7,14}$"
                          }
                        },
                        "required": [
                          "kind",
                          "e164"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 1,
                      "maxItems": 3,
                      "description": "Up to three phone numbers. Team members of the Hypamoon apps (kind user) are not available in the API."
                    },
                    "ringSeconds": {
                      "type": "integer",
                      "minimum": 5,
                      "maximum": 120,
                      "description": "How long to ring before onNoAnswer, 5 to 120 seconds."
                    },
                    "onNoAnswer": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "The node to go to when nobody answers."
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "strategy",
                    "targets",
                    "ringSeconds",
                    "onNoAnswer"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "menu"
                    },
                    "prompt": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 1000
                        }
                      },
                      "required": [
                        "text"
                      ],
                      "additionalProperties": false,
                      "description": "The text to read out, like \"Press 1 for sales\"."
                    },
                    "options": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9_-]{1,64}$"
                      },
                      "propertyNames": {
                        "enum": [
                          "0",
                          "1",
                          "2",
                          "3",
                          "4",
                          "5",
                          "6",
                          "7",
                          "8",
                          "9"
                        ]
                      },
                      "description": "The node per key, 0 to 9."
                    },
                    "onNoInput": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "The node to go to when the caller presses nothing."
                    },
                    "timeoutSeconds": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 30,
                      "description": "How long to wait for a key, 1 to 30 seconds."
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "prompt",
                    "options",
                    "onNoInput"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "voicemail"
                    },
                    "greeting": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 1000
                        }
                      },
                      "required": [
                        "text"
                      ],
                      "additionalProperties": false,
                      "description": "Optional text before the beep."
                    },
                    "transcribe": {
                      "type": "boolean",
                      "default": true,
                      "description": "Whether to transcribe the message. Default true."
                    }
                  },
                  "required": [
                    "id",
                    "type"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "ai_agent"
                    },
                    "onDone": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$",
                      "description": "Optional: the node to go to when the assistant is done. The assistant itself is set with PUT /assistant."
                    }
                  },
                  "required": [
                    "id",
                    "type"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{1,64}$"
                    },
                    "type": {
                      "type": "string",
                      "const": "hangup"
                    }
                  },
                  "required": [
                    "id",
                    "type"
                  ],
                  "additionalProperties": false
                }
              ],
              "discriminator": {
                "propertyName": "type"
              }
            },
            "description": "The nodes by id. The key must be the same as the node's id."
          }
        },
        "required": [
          "entry",
          "nodes"
        ],
        "additionalProperties": false,
        "description": "A call plan: a small graph. entry is the first node; every node points to the next by id. At most 50 nodes, and every node must be reachable from entry. Field names inside the plan are camelCase: the plan is stored exactly as you send it."
      },
      "CallPlanResponse": {
        "type": "object",
        "properties": {
          "number_id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer",
            "description": "The version of the number's own plan; 0 when the number has no own plan yet. Also in the ETag header."
          },
          "source": {
            "type": "string",
            "enum": [
              "number",
              "project",
              "none"
            ],
            "description": "Where plan comes from. number: the number has its own plan. project: the number has no own plan and follows the plan of the project (set_assistant sets that one up); this is what callers get. none: no plan at all."
          },
          "plan": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallPlan"
              },
              {
                "type": "null"
              }
            ],
            "description": "The plan that calls to this number follow: its own plan, or else the plan of the project; null when there is none."
          }
        },
        "required": [
          "number_id",
          "version",
          "source",
          "plan"
        ],
        "additionalProperties": false
      },
      "CallPlanSaved": {
        "type": "object",
        "properties": {
          "number_id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer",
            "description": "The version of the number's own plan; 0 when the number has no own plan yet. Also in the ETag header."
          },
          "plan": {
            "$ref": "#/components/schemas/CallPlan"
          },
          "assistant_sync": {
            "type": "string",
            "enum": [
              "synced",
              "failed",
              "not_needed",
              "skipped_test_mode"
            ],
            "description": "Whether the assistant was updated with this plan. not_needed for a plan without ai_agent; skipped_test_mode with a test key."
          }
        },
        "required": [
          "number_id",
          "version",
          "plan",
          "assistant_sync"
        ],
        "additionalProperties": false
      },
      "Call": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the call."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ],
            "description": "inbound, or outbound for a call your assistant made with POST /calls."
          },
          "from": {
            "type": [
              "string",
              "null"
            ],
            "description": "The caller, in E.164."
          },
          "to": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number that was called, in E.164."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The status as we track it: ringing, active, completed, missed, failed, voicemail, ai_handled. New values may be added; treat an unknown value like completed."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call reached us."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call started."
          },
          "answered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the platform picked up. With a call plan the platform always picks up first, so this does not prove a person answered."
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call ended."
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The length of the call in seconds."
          },
          "hangup_cause": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the call ended, as reported by the carrier, like normal_clearing."
          },
          "has_summary": {
            "type": "boolean",
            "description": "Whether the call has a summary. Fetch it with GET /calls/{id}."
          },
          "summary_ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the summary of a recorded call was ready. Empty for a call handled by the voice AI; look at has_summary instead."
          },
          "test": {
            "type": "boolean",
            "description": "true for a simulated call from POST /test/calls or POST /calls with a test key (test mode). Also in webhooks, so you can keep test traffic apart."
          },
          "outbound": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "ringing",
                  "in_progress",
                  "completed",
                  "failed",
                  "no_answer",
                  "busy"
                ],
                "description": "How the outbound call went: queued (not dialled yet), ringing, in_progress (answered), completed, failed, no_answer or busy. An answering machine counts as no_answer, with answered_by machine. Only moves forward."
              },
              "answered_by": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "human",
                  "machine",
                  null
                ],
                "description": "Who picked up, as far as answering machine detection could tell; null when nobody answered or before it is known."
              },
              "voicemail_left": {
                "type": "boolean",
                "description": "Whether the assistant left a message on an answering machine."
              },
              "on_voicemail": {
                "type": "string",
                "enum": [
                  "hangup",
                  "leave_message"
                ],
                "description": "What you asked the assistant to do when an answering machine picks up."
              },
              "max_duration_sec": {
                "type": "integer",
                "description": "The cap on the length of the call you asked for, in seconds."
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own reference from POST /calls."
              },
              "metadata": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Your own metadata from POST /calls."
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "With status failed: a short code, like carrier_rejected, assistant_start_failed or unallocated_number. Otherwise null."
              },
              "purpose": {
                "type": "string",
                "enum": [
                  "transactional",
                  "marketing"
                ],
                "description": "Why the call was made, as sent with POST /calls (transactional when left out). A marketing call was only placed on recorded consent."
              }
            },
            "required": [
              "status",
              "answered_by",
              "voicemail_left",
              "on_voicemail",
              "max_duration_sec",
              "reference",
              "metadata",
              "error",
              "purpose"
            ],
            "additionalProperties": false,
            "description": "The request behind an outbound call started with POST /calls."
          }
        },
        "required": [
          "id",
          "direction",
          "from",
          "to",
          "status",
          "created_at",
          "started_at",
          "answered_at",
          "ended_at",
          "duration_sec",
          "hangup_cause",
          "has_summary",
          "summary_ready_at",
          "test",
          "outbound"
        ],
        "additionalProperties": false,
        "description": "A call. The same shape in GET /calls, GET /calls/{id} and every webhook."
      },
      "CallDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the call."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ],
            "description": "inbound, or outbound for a call your assistant made with POST /calls."
          },
          "from": {
            "type": [
              "string",
              "null"
            ],
            "description": "The caller, in E.164."
          },
          "to": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number that was called, in E.164."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The status as we track it: ringing, active, completed, missed, failed, voicemail, ai_handled. New values may be added; treat an unknown value like completed."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call reached us."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call started."
          },
          "answered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the platform picked up. With a call plan the platform always picks up first, so this does not prove a person answered."
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the call ended."
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The length of the call in seconds."
          },
          "hangup_cause": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the call ended, as reported by the carrier, like normal_clearing."
          },
          "has_summary": {
            "type": "boolean",
            "description": "Whether the call has a summary. Fetch it with GET /calls/{id}."
          },
          "summary_ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the summary of a recorded call was ready. Empty for a call handled by the voice AI; look at has_summary instead."
          },
          "test": {
            "type": "boolean",
            "description": "true for a simulated call from POST /test/calls or POST /calls with a test key (test mode). Also in webhooks, so you can keep test traffic apart."
          },
          "outbound": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "ringing",
                  "in_progress",
                  "completed",
                  "failed",
                  "no_answer",
                  "busy"
                ],
                "description": "How the outbound call went: queued (not dialled yet), ringing, in_progress (answered), completed, failed, no_answer or busy. An answering machine counts as no_answer, with answered_by machine. Only moves forward."
              },
              "answered_by": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "human",
                  "machine",
                  null
                ],
                "description": "Who picked up, as far as answering machine detection could tell; null when nobody answered or before it is known."
              },
              "voicemail_left": {
                "type": "boolean",
                "description": "Whether the assistant left a message on an answering machine."
              },
              "on_voicemail": {
                "type": "string",
                "enum": [
                  "hangup",
                  "leave_message"
                ],
                "description": "What you asked the assistant to do when an answering machine picks up."
              },
              "max_duration_sec": {
                "type": "integer",
                "description": "The cap on the length of the call you asked for, in seconds."
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own reference from POST /calls."
              },
              "metadata": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Your own metadata from POST /calls."
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "With status failed: a short code, like carrier_rejected, assistant_start_failed or unallocated_number. Otherwise null."
              },
              "purpose": {
                "type": "string",
                "enum": [
                  "transactional",
                  "marketing"
                ],
                "description": "Why the call was made, as sent with POST /calls (transactional when left out). A marketing call was only placed on recorded consent."
              }
            },
            "required": [
              "status",
              "answered_by",
              "voicemail_left",
              "on_voicemail",
              "max_duration_sec",
              "reference",
              "metadata",
              "error",
              "purpose"
            ],
            "additionalProperties": false,
            "description": "The request behind an outbound call started with POST /calls."
          },
          "transcript": {
            "type": [
              "string",
              "null"
            ],
            "description": "The transcript. Only here, never in a webhook."
          },
          "summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Summary"
              },
              {
                "type": "null"
              }
            ],
            "description": "The summary of a call."
          }
        },
        "required": [
          "id",
          "direction",
          "from",
          "to",
          "status",
          "created_at",
          "started_at",
          "answered_at",
          "ended_at",
          "duration_sec",
          "hangup_cause",
          "has_summary",
          "summary_ready_at",
          "test",
          "outbound",
          "transcript",
          "summary"
        ],
        "additionalProperties": false,
        "description": "A call with its transcript and summary."
      },
      "CallList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Call"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there is a next page."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass this as cursor to get the next page. Null on the last page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "Summary": {
        "type": "object",
        "properties": {
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "The summary in a few sentences."
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the call was about."
          },
          "sentiment": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "positive",
              "neutral",
              "negative",
              null
            ],
            "description": "The mood of the caller."
          },
          "action_items": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What someone has to do after this call."
          },
          "next_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "The agreed next step, if any."
          }
        },
        "required": [
          "text",
          "topics",
          "sentiment",
          "action_items",
          "next_step"
        ],
        "additionalProperties": false,
        "description": "The summary of a call."
      },
      "TestCallInput": {
        "type": "object",
        "properties": {
          "number_id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of one of your test numbers, from GET /numbers."
          },
          "scenario": {
            "type": "string",
            "enum": [
              "reservation",
              "callback_request",
              "question",
              "missed"
            ],
            "description": "What happens in the call: reservation, callback_request, question, missed. missed has no transcript and no summary, so only call.completed is sent."
          },
          "from": {
            "type": "string",
            "description": "Optional: the caller, a fictional number from +447700900000 to +447700900999."
          },
          "caller_goal": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300
          }
        },
        "required": [
          "number_id",
          "scenario"
        ],
        "additionalProperties": false
      },
      "WebhookEndpointInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2048,
            "description": "An https URL with a domain name on the default port, without credentials, that resolves to a public address."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "call.completed",
                "call.summary.ready",
                "message.delivered",
                "message.failed",
                "conversation.summary.ready",
                "balance.low",
                "balance.topped_up",
                "balance.reload_failed",
                "document.created",
                "email.sent",
                "email.delivered",
                "email.delayed",
                "email.bounced",
                "email.complained"
              ]
            },
            "minItems": 1,
            "description": "The events to receive, from call.completed, call.summary.ready, message.delivered, message.failed, conversation.summary.ready, balance.low, balance.topped_up, balance.reload_failed, document.created, email.sent, email.delivered, email.delayed, email.bounced, email.complained. The email events come once per recipient."
          },
          "app_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional, for email events only: receive only the email events of emails this app sent (an id from list_apps). Without it, the endpoint gets the events of every email of the project."
          }
        },
        "required": [
          "url",
          "events"
        ],
        "description": "A new webhook endpoint."
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The subscribed events, from call.completed, call.summary.ready, message.delivered, message.failed, conversation.summary.ready, balance.low, balance.topped_up, balance.reload_failed, document.created."
          },
          "active": {
            "type": "boolean",
            "description": "false after 50 failed deliveries in a row; switch it back on with POST /webhooks/{id}/enable."
          },
          "secret_prefix": {
            "type": "string",
            "description": "The first characters of the secret, to recognize it."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the endpoint was created."
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The last delivery that got a 2xx."
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The last delivery that failed."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Failed deliveries in a row."
          },
          "app_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a filter on one app: the endpoint gets the email events of that app's emails only."
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active",
          "secret_prefix",
          "created_at",
          "last_success_at",
          "last_failure_at",
          "consecutive_failures"
        ],
        "additionalProperties": false,
        "description": "A webhook endpoint. Never with the secret."
      },
      "WebhookEndpointCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The subscribed events, from call.completed, call.summary.ready, message.delivered, message.failed, conversation.summary.ready, balance.low, balance.topped_up, balance.reload_failed, document.created."
          },
          "active": {
            "type": "boolean",
            "description": "false after 50 failed deliveries in a row; switch it back on with POST /webhooks/{id}/enable."
          },
          "secret_prefix": {
            "type": "string",
            "description": "The first characters of the secret, to recognize it."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the endpoint was created."
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The last delivery that got a 2xx."
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The last delivery that failed."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Failed deliveries in a row."
          },
          "app_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a filter on one app: the endpoint gets the email events of that app's emails only."
          },
          "secret": {
            "type": "string",
            "description": "The signing secret, whsec_ plus 43 characters. Shown only in this response: store it now."
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active",
          "secret_prefix",
          "created_at",
          "last_success_at",
          "last_failure_at",
          "consecutive_failures",
          "secret"
        ],
        "additionalProperties": false,
        "description": "The new webhook endpoint, with its secret."
      },
      "WebhookEndpointList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "EmailSentEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id, evt_ plus 32 hex characters. The same on retries."
          },
          "type": {
            "type": "string",
            "const": "email.sent"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "email_id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the email, as sendEmail, POST /emails and list_emails return it."
              },
              "app_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The app that sent it; null when it was sent with the API, by an agent or from the portal."
              },
              "recipient": {
                "type": "string",
                "description": "The recipient this event is about. An email to several people sends one event per recipient."
              },
              "recipient_field": {
                "type": "string",
                "enum": [
                  "to",
                  "cc",
                  "bcc"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "sent",
                  "delivered",
                  "bounced",
                  "complained"
                ],
                "description": "The recipient's status after this event; sent for email.delayed."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A short reason in plain words for email.delayed, email.bounced and email.complained."
              },
              "occurred_at": {
                "type": "string",
                "description": "When it happened (ISO 8601)."
              },
              "test": {
                "type": "boolean",
                "description": "true for an email sent with a test key: nothing was sent, but email.sent and email.delivered still arrive, so you can test your receiver."
              }
            },
            "required": [
              "email_id",
              "app_id",
              "recipient",
              "recipient_field",
              "status",
              "reason",
              "occurred_at",
              "test"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "EmailDeliveredEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id, evt_ plus 32 hex characters. The same on retries."
          },
          "type": {
            "type": "string",
            "const": "email.delivered"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "email_id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the email, as sendEmail, POST /emails and list_emails return it."
              },
              "app_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The app that sent it; null when it was sent with the API, by an agent or from the portal."
              },
              "recipient": {
                "type": "string",
                "description": "The recipient this event is about. An email to several people sends one event per recipient."
              },
              "recipient_field": {
                "type": "string",
                "enum": [
                  "to",
                  "cc",
                  "bcc"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "sent",
                  "delivered",
                  "bounced",
                  "complained"
                ],
                "description": "The recipient's status after this event; sent for email.delayed."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A short reason in plain words for email.delayed, email.bounced and email.complained."
              },
              "occurred_at": {
                "type": "string",
                "description": "When it happened (ISO 8601)."
              },
              "test": {
                "type": "boolean",
                "description": "true for an email sent with a test key: nothing was sent, but email.sent and email.delivered still arrive, so you can test your receiver."
              }
            },
            "required": [
              "email_id",
              "app_id",
              "recipient",
              "recipient_field",
              "status",
              "reason",
              "occurred_at",
              "test"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "EmailDelayedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id, evt_ plus 32 hex characters. The same on retries."
          },
          "type": {
            "type": "string",
            "const": "email.delayed"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "email_id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the email, as sendEmail, POST /emails and list_emails return it."
              },
              "app_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The app that sent it; null when it was sent with the API, by an agent or from the portal."
              },
              "recipient": {
                "type": "string",
                "description": "The recipient this event is about. An email to several people sends one event per recipient."
              },
              "recipient_field": {
                "type": "string",
                "enum": [
                  "to",
                  "cc",
                  "bcc"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "sent",
                  "delivered",
                  "bounced",
                  "complained"
                ],
                "description": "The recipient's status after this event; sent for email.delayed."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A short reason in plain words for email.delayed, email.bounced and email.complained."
              },
              "occurred_at": {
                "type": "string",
                "description": "When it happened (ISO 8601)."
              },
              "test": {
                "type": "boolean",
                "description": "true for an email sent with a test key: nothing was sent, but email.sent and email.delivered still arrive, so you can test your receiver."
              }
            },
            "required": [
              "email_id",
              "app_id",
              "recipient",
              "recipient_field",
              "status",
              "reason",
              "occurred_at",
              "test"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "EmailBouncedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id, evt_ plus 32 hex characters. The same on retries."
          },
          "type": {
            "type": "string",
            "const": "email.bounced"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "email_id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the email, as sendEmail, POST /emails and list_emails return it."
              },
              "app_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The app that sent it; null when it was sent with the API, by an agent or from the portal."
              },
              "recipient": {
                "type": "string",
                "description": "The recipient this event is about. An email to several people sends one event per recipient."
              },
              "recipient_field": {
                "type": "string",
                "enum": [
                  "to",
                  "cc",
                  "bcc"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "sent",
                  "delivered",
                  "bounced",
                  "complained"
                ],
                "description": "The recipient's status after this event; sent for email.delayed."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A short reason in plain words for email.delayed, email.bounced and email.complained."
              },
              "occurred_at": {
                "type": "string",
                "description": "When it happened (ISO 8601)."
              },
              "test": {
                "type": "boolean",
                "description": "true for an email sent with a test key: nothing was sent, but email.sent and email.delivered still arrive, so you can test your receiver."
              }
            },
            "required": [
              "email_id",
              "app_id",
              "recipient",
              "recipient_field",
              "status",
              "reason",
              "occurred_at",
              "test"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "EmailComplainedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id, evt_ plus 32 hex characters. The same on retries."
          },
          "type": {
            "type": "string",
            "const": "email.complained"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "email_id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the email, as sendEmail, POST /emails and list_emails return it."
              },
              "app_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The app that sent it; null when it was sent with the API, by an agent or from the portal."
              },
              "recipient": {
                "type": "string",
                "description": "The recipient this event is about. An email to several people sends one event per recipient."
              },
              "recipient_field": {
                "type": "string",
                "enum": [
                  "to",
                  "cc",
                  "bcc"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "sent",
                  "delivered",
                  "bounced",
                  "complained"
                ],
                "description": "The recipient's status after this event; sent for email.delayed."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A short reason in plain words for email.delayed, email.bounced and email.complained."
              },
              "occurred_at": {
                "type": "string",
                "description": "When it happened (ISO 8601)."
              },
              "test": {
                "type": "boolean",
                "description": "true for an email sent with a test key: nothing was sent, but email.sent and email.delivered still arrive, so you can test your receiver."
              }
            },
            "required": [
              "email_id",
              "app_id",
              "recipient",
              "recipient_field",
              "status",
              "reason",
              "occurred_at",
              "test"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "WebhookTestResult": {
        "type": "object",
        "properties": {
          "delivery_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_id": {
            "type": "string",
            "description": "The id of the webhook.test event, evt_ plus 32 hex characters."
          },
          "status": {
            "type": "string",
            "enum": [
              "delivered",
              "pending",
              "failed"
            ],
            "description": "delivered: your endpoint answered 2xx. pending: it did not, and we retry with backoff. failed: we gave up."
          },
          "response_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The HTTP status your endpoint answered with, if any."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "timeout",
              "connection_failed",
              "non_2xx_status",
              "not_public_host",
              "invalid_url",
              "response_too_large",
              "secret_unreadable",
              "endpoint_disabled",
              "call_deleted",
              "summary_deleted",
              "message_deleted",
              "conversation_deleted",
              "document_deleted",
              "invalid_reference",
              "delivery_failed",
              null
            ],
            "description": "Why the delivery did not succeed, as a short code."
          }
        },
        "required": [
          "delivery_id",
          "event_id",
          "status",
          "response_status",
          "error"
        ],
        "additionalProperties": false
      },
      "CallCompletedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "call.completed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "call": {
                "$ref": "#/components/schemas/Call"
              }
            },
            "required": [
              "call"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent after a call ends."
      },
      "CallSummaryReadyEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "call.summary.ready"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "call": {
                "$ref": "#/components/schemas/Call"
              },
              "summary": {
                "$ref": "#/components/schemas/Summary"
              }
            },
            "required": [
              "call",
              "summary"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when the summary of a call is ready. Without the transcript: fetch that with GET /calls/{data.call.id}."
      },
      "WebhookTestEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "webhook.test"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "endpoint_id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "endpoint_id"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent by POST /webhooks/{id}/test. Needs no subscription."
      },
      "CallInput": {
        "type": "object",
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{6,14}$",
            "description": "The person to call, in E.164. Live: a mobile or geographic number in a country your project can call (otherwise destination_not_allowed with a reason). Test mode: a test number from +447700900000 to +447700900999."
          },
          "from": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{6,14}$",
            "description": "Optional: which of your project's numbers to call from. Default: your first active number. The person sees this number and can call it back."
          },
          "assistant_instructions": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "pattern": "^[^\\u0000-\\u0009\\u000B\\u000C\\u000E-\\u001F\\u007F]*$",
            "description": "Optional: what this call is about, on top of the assistant's own instructions. For example: \"Confirm the appointment of Sam Taylor tomorrow at 10:00 at 40 Main Street. If Sam cannot make it, ask for a better day and say we will confirm by SMS.\" Treated as context, never as a way to change the rules: the assistant always says it is a digital assistant."
          },
          "on_voicemail": {
            "type": "string",
            "enum": [
              "hangup",
              "leave_message"
            ],
            "description": "What to do when an answering machine picks up: hang up (default) or leave a short message after the beep."
          },
          "voicemail_message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "Optional, with on_voicemail leave_message: the message after the opening sentence (which always says it is the digital assistant of your company). Default: we tried to reach you, call us back on this number."
          },
          "max_duration_sec": {
            "type": "integer",
            "minimum": 30,
            "maximum": 1200,
            "description": "Optional: the call ends after this many seconds of talking. Default 600, at most 1200."
          },
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "Optional: your own id for this call, like an appointment id. Comes back on the call and in its webhooks."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "propertyNames": {
              "minLength": 1,
              "maxLength": 40
            },
            "description": "Optional: up to 20 string key-value pairs for your own use. Come back on the call and in its webhooks; the assistant never sees them."
          },
          "calling_window": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):([0-5]\\d)$"
              },
              "end": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):([0-5]\\d)$"
              },
              "days": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "mon",
                    "tue",
                    "wed",
                    "thu",
                    "fri",
                    "sat",
                    "sun"
                  ]
                },
                "minItems": 1,
                "maxItems": 7
              }
            },
            "required": [
              "start",
              "end"
            ],
            "additionalProperties": false,
            "description": "When the assistant may call, in the local time of the person called. Default 09:00 to 20:00, Monday to Saturday. Never before 08:00 or after 21:00."
          },
          "purpose": {
            "type": "string",
            "enum": [
              "transactional",
              "marketing"
            ],
            "description": "Why you call. transactional (default): an answer to the person's own question, confirming or reminding of an appointment, an order or delivery status, or a callback they asked for. marketing: anything that sells or promotes; only allowed when consent from this person is recorded (POST /api/v1/call-consents), otherwise consent_required. Recorded with the call."
          }
        },
        "required": [
          "to"
        ],
        "additionalProperties": false,
        "description": "An outbound call by your project's assistant to one person who expects it."
      },
      "CallConsentInput": {
        "type": "object",
        "properties": {
          "phone_number": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{6,14}$",
            "description": "The number of the person who gave consent, in E.164. Stored only as a keyed hash plus its last two digits."
          },
          "source": {
            "type": "string",
            "enum": [
              "form",
              "call",
              "import",
              "manual"
            ],
            "description": "Where the consent came from: form (a form the person filled in), call (they said yes on a call), import (from your own records), manual (recorded by hand)."
          },
          "evidence": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "A reference to the proof, so it can be shown later: a form entry id, a call id, a file and row, or a short note. Not the proof itself and no other personal data."
          },
          "consented_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the person gave consent (ISO 8601). Default: now. Cannot be in the future."
          }
        },
        "required": [
          "phone_number",
          "source",
          "evidence"
        ],
        "additionalProperties": false,
        "description": "Consent from one person to receive marketing calls from this project."
      },
      "CallConsent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the consent."
          },
          "phone_number_ending": {
            "type": "string",
            "pattern": "^\\d{2}$",
            "description": "The last two digits of the number. The number itself is never stored, only a keyed hash."
          },
          "source": {
            "type": "string",
            "enum": [
              "form",
              "call",
              "import",
              "manual"
            ],
            "description": "Where the consent came from: form, call, import or manual."
          },
          "evidence": {
            "type": "string",
            "description": "Your reference to the proof."
          },
          "consented_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the person gave consent."
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was recorded here."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was revoked; null while it is valid."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ],
            "description": "active: marketing calls to this number are allowed. revoked: no longer."
          }
        },
        "required": [
          "id",
          "phone_number_ending",
          "source",
          "evidence",
          "consented_at",
          "recorded_at",
          "revoked_at",
          "status"
        ],
        "additionalProperties": false,
        "description": "Recorded consent from one person to receive marketing calls from this project."
      },
      "CallConsentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CallConsent"
            },
            "description": "The consents, newest first (at most 100)."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false,
        "description": "The project's recorded consents."
      },
      "MessageInput": {
        "type": "object",
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{6,14}$",
            "description": "The recipient in E.164. Live: a mobile number in a country your project can text (otherwise destination_not_allowed with a reason). Test mode: a test number from +447700900000 to +447700900999."
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1600,
            "description": "The text. At most 6 SMS segments: 918 characters in plain text (GSM-7), 402 with an emoji or another character outside GSM-7. Typographic quotes and dashes are replaced by plain ones."
          },
          "from": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32,
            "description": "Optional sender name: 1 to 11 letters, digits and spaces, with at least one letter. Default: your project name. A phone number is not possible."
          },
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "Optional: your own id for this message, like an order number. Comes back in the message and its webhooks."
          }
        },
        "required": [
          "to",
          "body"
        ],
        "additionalProperties": false,
        "description": "A transactional SMS to one recipient."
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the message."
          },
          "to": {
            "type": "string",
            "description": "The recipient, in E.164."
          },
          "from": {
            "type": "string",
            "description": "The sender the recipient sees: an alphanumeric name of up to 11 characters."
          },
          "body": {
            "type": "string",
            "description": "The text as it was sent, after typographic quotes and dashes were made SMS-safe."
          },
          "segments": {
            "type": "integer",
            "description": "How many SMS segments the text took. A segment is 160 GSM-7 or 70 UCS-2 characters (153 and 67 when split)."
          },
          "encoding": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "gsm7",
              "ucs2",
              null
            ],
            "description": "gsm7, or ucs2 when the text has a character outside GSM-7 (an emoji, for example), which makes every segment shorter."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "delivered",
              "failed"
            ],
            "description": "queued: accepted. sent: handed to the mobile network. delivered: the phone confirmed it. failed: it did not arrive; see error."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "With status failed: a short code, like delivery_failed or sending_failed. Otherwise null."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id for this message, as you sent it (an order number, for example)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When we accepted the message."
          },
          "status_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the status last changed."
          },
          "test": {
            "type": "boolean",
            "description": "true for a message sent with a test key to a test number. It never reaches a phone."
          }
        },
        "required": [
          "id",
          "to",
          "from",
          "body",
          "segments",
          "encoding",
          "status",
          "error",
          "reference",
          "created_at",
          "status_updated_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "An SMS message. The same shape in POST /messages, GET /messages, GET /messages/{id} and the message webhooks."
      },
      "MessageList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there is another page."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass this as cursor to get the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "MessageDeliveredEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "message.delivered"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "message": {
                "$ref": "#/components/schemas/Message"
              }
            },
            "required": [
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when the phone of the recipient confirmed the message."
      },
      "MessageFailedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "message.failed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "message": {
                "$ref": "#/components/schemas/Message"
              }
            },
            "required": [
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when a message could not be delivered. data.message.error says why."
      },
      "ConversationInput": {
        "type": "object",
        "properties": {
          "channel": {
            "type": "string",
            "const": "chat",
            "description": "The channel. Only chat for now; it is also the default."
          },
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "pattern": "^[^\\u0000-\\u001F\\u007F]*$",
            "description": "Optional: your own id for this conversation, like a session or ticket id. Comes back in the conversation and its webhook."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 500
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                }
              ]
            },
            "propertyNames": {
              "minLength": 1,
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Optional: up to 20 keys with a string (at most 500 characters), number or boolean, for your own use. Stored and returned as is; the assistant never sees it."
          },
          "customer": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100,
                "pattern": "^[^\\u0000-\\u001F\\u007F]*$"
              },
              "phone": {
                "type": "string",
                "minLength": 1,
                "maxLength": 32,
                "pattern": "^[0-9+()\\s.-]+$"
              },
              "email": {
                "type": "string",
                "maxLength": 254,
                "format": "email"
              }
            },
            "additionalProperties": false,
            "description": "Optional: what your app already knows about the customer, for example from their login. The assistant may use it to address the customer and to confirm how to reach them; it is treated as data, never as instructions."
          }
        },
        "additionalProperties": false,
        "description": "A new chat conversation with the assistant of the project."
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the conversation."
          },
          "channel": {
            "type": "string",
            "enum": [
              "chat",
              "widget"
            ],
            "description": "The channel: chat when your app started it with an API key, widget when a visitor started it in the chat widget on a website (public chat token)."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ],
            "description": "open: takes messages. closed: takes no more messages; the summary follows."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id for this conversation, as you sent it."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                }
              ]
            },
            "description": "Your metadata, as you sent it. The assistant never sees it."
          },
          "customer": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name",
              "phone",
              "email"
            ],
            "additionalProperties": false,
            "description": "What your app told us about the customer when the conversation started."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opened in (nl, en, de, fr or es). It replies in the customer's language when that is one of its languages."
          },
          "turns": {
            "type": "integer",
            "description": "Customer messages answered so far."
          },
          "max_turns": {
            "type": "integer",
            "description": "After this many turns the conversation closes (close_reason turn_limit)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the conversation started."
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last message was sent."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the conversation closed."
          },
          "close_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "closed",
              "inactive",
              "turn_limit",
              null
            ],
            "description": "closed: POST /conversations/{id}/close. inactive: no message for 30 minutes. turn_limit: max_turns reached."
          },
          "has_summary": {
            "type": "boolean",
            "description": "Whether the summary is ready. Fetch it with GET /conversations/{id}."
          },
          "summary_ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the summary was ready."
          },
          "test": {
            "type": "boolean",
            "description": "true for a conversation in test mode (hm_test_): real replies, free, with a low daily limit."
          }
        },
        "required": [
          "id",
          "channel",
          "status",
          "reference",
          "metadata",
          "customer",
          "language",
          "turns",
          "max_turns",
          "created_at",
          "last_message_at",
          "closed_at",
          "close_reason",
          "has_summary",
          "summary_ready_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "A chat conversation with the assistant. The same shape in every response and in conversation.summary.ready."
      },
      "ConversationDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the conversation."
          },
          "channel": {
            "type": "string",
            "enum": [
              "chat",
              "widget"
            ],
            "description": "The channel: chat when your app started it with an API key, widget when a visitor started it in the chat widget on a website (public chat token)."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ],
            "description": "open: takes messages. closed: takes no more messages; the summary follows."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id for this conversation, as you sent it."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                }
              ]
            },
            "description": "Your metadata, as you sent it. The assistant never sees it."
          },
          "customer": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name",
              "phone",
              "email"
            ],
            "additionalProperties": false,
            "description": "What your app told us about the customer when the conversation started."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opened in (nl, en, de, fr or es). It replies in the customer's language when that is one of its languages."
          },
          "turns": {
            "type": "integer",
            "description": "Customer messages answered so far."
          },
          "max_turns": {
            "type": "integer",
            "description": "After this many turns the conversation closes (close_reason turn_limit)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the conversation started."
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last message was sent."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the conversation closed."
          },
          "close_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "closed",
              "inactive",
              "turn_limit",
              null
            ],
            "description": "closed: POST /conversations/{id}/close. inactive: no message for 30 minutes. turn_limit: max_turns reached."
          },
          "has_summary": {
            "type": "boolean",
            "description": "Whether the summary is ready. Fetch it with GET /conversations/{id}."
          },
          "summary_ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the summary was ready."
          },
          "test": {
            "type": "boolean",
            "description": "true for a conversation in test mode (hm_test_): real replies, free, with a low daily limit."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "All messages, oldest first, starting with the fixed greeting."
          },
          "summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Summary"
              },
              {
                "type": "null"
              }
            ],
            "description": "The summary, after the conversation closed. Same shape as the summary of a call."
          }
        },
        "required": [
          "id",
          "channel",
          "status",
          "reference",
          "metadata",
          "customer",
          "language",
          "turns",
          "max_turns",
          "created_at",
          "last_message_at",
          "closed_at",
          "close_reason",
          "has_summary",
          "summary_ready_at",
          "test",
          "messages",
          "summary"
        ],
        "additionalProperties": false,
        "description": "A conversation with its messages and summary."
      },
      "ConversationList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there is another page."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass this as cursor to get the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "ChatMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the message."
          },
          "role": {
            "type": "string",
            "enum": [
              "customer",
              "assistant"
            ],
            "description": "Who wrote it: the customer, or the assistant (also the fixed greeting)."
          },
          "text": {
            "type": "string",
            "description": "The text."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the message was stored."
          }
        },
        "required": [
          "id",
          "role",
          "text",
          "created_at"
        ],
        "additionalProperties": false,
        "description": "One message in a conversation."
      },
      "ChatMessageInput": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "What the customer typed, at most 2000 characters. Send it as is; it is never treated as an instruction to the assistant."
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "description": "One message from the customer."
      },
      "ChatTurn": {
        "type": "object",
        "properties": {
          "message": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the message."
              },
              "role": {
                "type": "string",
                "enum": [
                  "customer",
                  "assistant"
                ],
                "description": "Who wrote it: the customer, or the assistant (also the fixed greeting)."
              },
              "text": {
                "type": "string",
                "description": "The text."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the message was stored."
              }
            },
            "required": [
              "id",
              "role",
              "text",
              "created_at"
            ],
            "additionalProperties": false,
            "description": "The customer's message as stored."
          },
          "reply": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the message."
              },
              "role": {
                "type": "string",
                "enum": [
                  "customer",
                  "assistant"
                ],
                "description": "Who wrote it: the customer, or the assistant (also the fixed greeting)."
              },
              "text": {
                "type": "string",
                "description": "The text."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the message was stored."
              }
            },
            "required": [
              "id",
              "role",
              "text",
              "created_at"
            ],
            "additionalProperties": false,
            "description": "The assistant's reply. Show this text to the customer."
          },
          "conversation": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the conversation."
              },
              "channel": {
                "type": "string",
                "enum": [
                  "chat",
                  "widget"
                ],
                "description": "The channel: chat when your app started it with an API key, widget when a visitor started it in the chat widget on a website (public chat token)."
              },
              "status": {
                "type": "string",
                "enum": [
                  "open",
                  "closed"
                ],
                "description": "open: takes messages. closed: takes no more messages; the summary follows."
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own id for this conversation, as you sent it."
              },
              "metadata": {
                "type": "object",
                "additionalProperties": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    }
                  ]
                },
                "description": "Your metadata, as you sent it. The assistant never sees it."
              },
              "customer": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "name",
                  "phone",
                  "email"
                ],
                "additionalProperties": false,
                "description": "What your app told us about the customer when the conversation started."
              },
              "language": {
                "type": "string",
                "description": "The language the assistant opened in (nl, en, de, fr or es). It replies in the customer's language when that is one of its languages."
              },
              "turns": {
                "type": "integer",
                "description": "Customer messages answered so far."
              },
              "max_turns": {
                "type": "integer",
                "description": "After this many turns the conversation closes (close_reason turn_limit)."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the conversation started."
              },
              "last_message_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the last message was sent."
              },
              "closed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the conversation closed."
              },
              "close_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "closed",
                  "inactive",
                  "turn_limit",
                  null
                ],
                "description": "closed: POST /conversations/{id}/close. inactive: no message for 30 minutes. turn_limit: max_turns reached."
              },
              "has_summary": {
                "type": "boolean",
                "description": "Whether the summary is ready. Fetch it with GET /conversations/{id}."
              },
              "summary_ready_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the summary was ready."
              },
              "test": {
                "type": "boolean",
                "description": "true for a conversation in test mode (hm_test_): real replies, free, with a low daily limit."
              }
            },
            "required": [
              "id",
              "channel",
              "status",
              "reference",
              "metadata",
              "customer",
              "language",
              "turns",
              "max_turns",
              "created_at",
              "last_message_at",
              "closed_at",
              "close_reason",
              "has_summary",
              "summary_ready_at",
              "test"
            ],
            "additionalProperties": false,
            "description": "The conversation after this turn. status closed means the turn limit was reached."
          }
        },
        "required": [
          "message",
          "reply",
          "conversation"
        ],
        "additionalProperties": false,
        "description": "One turn: the customer's message and the assistant's reply."
      },
      "ConversationSummaryReadyEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "conversation.summary.ready"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "conversation": {
                "$ref": "#/components/schemas/Conversation"
              },
              "summary": {
                "$ref": "#/components/schemas/Summary"
              }
            },
            "required": [
              "conversation",
              "summary"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when the summary of a closed chat conversation is ready. Without the messages: fetch those with GET /conversations/{data.conversation.id}."
      },
      "ChatTokenInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "A name for your own overview, like the website it is for."
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "The name the assistant uses for the business in the chat window, like \"Knip\": in the greeting (\"You are chatting with the digital assistant of Knip.\") and in its answers. Leave it out or null to use the name of the site the widget is on, and otherwise the project name."
          },
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "maxItems": 10,
            "description": "The exact origins that may use this token, like https://www.example.com (scheme and host, no path, no wildcard), at most 10. A live token needs at least one https origin. A test token may always be used from localhost."
          },
          "turnstile": {
            "type": "boolean",
            "description": "Ask for a bot check before a conversation starts. Default true.",
            "default": true
          },
          "max_conversations_per_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "description": "New conversations per day through this token. Default 200, at most 1000.",
            "default": 200
          }
        },
        "required": [
          "name",
          "allowed_origins"
        ],
        "additionalProperties": false,
        "description": "A new public chat token for the website widget."
      },
      "ChatTokenUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "The name the assistant uses for the business in the chat window, like \"Knip\": in the greeting (\"You are chatting with the digital assistant of Knip.\") and in its answers. null or an empty string goes back to the name of the site the widget is on, and otherwise the project name."
          },
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "maxItems": 10
          },
          "turnstile": {
            "type": "boolean"
          },
          "max_conversations_per_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        },
        "additionalProperties": false,
        "description": "Change a public chat token. Fields you leave out stay as they are."
      },
      "ChatToken": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the public chat token. Use it to change or revoke the token."
          },
          "name": {
            "type": "string",
            "description": "Your name for the token, like the website it is for."
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name the assistant uses for the business in the chat window and its greeting, or null: then the name of the site the widget is on, and otherwise the project name."
          },
          "token": {
            "type": "string",
            "pattern": "^hm_pub(test)?_[0-9A-Za-z]{24}$",
            "description": "The public chat token: hm_pub_ (live) or hm_pubtest_ (test project) plus 24 characters. Not a secret: it goes in the page source of your website."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "live or test, from the project the key belongs to."
          },
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The exact origins that may use the token, like https://www.example.com. A test token also works from localhost."
          },
          "turnstile": {
            "type": "boolean",
            "description": "Whether a visitor passes a bot check before a conversation starts."
          },
          "max_conversations_per_day": {
            "type": "integer",
            "description": "New conversations per day through this token, counted until midnight, Europe/Amsterdam."
          },
          "revoked": {
            "type": "boolean",
            "description": "true once the token is revoked: the widget stops working right away, also in open conversations."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the token was revoked."
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a browser last used the token, to the minute."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the token was created."
          },
          "embed_code": {
            "type": "string",
            "description": "The script tag for your website, with this token in it. Paste it before </body>."
          },
          "kind": {
            "type": "string",
            "enum": [
              "chat",
              "form"
            ],
            "description": "chat: the chat widget, the cart, the checkout and forms. form: only forms that post to a collection with public_write (made by Hypamoon for a form on a site); it cannot chat."
          }
        },
        "required": [
          "id",
          "name",
          "display_name",
          "token",
          "mode",
          "allowed_origins",
          "turnstile",
          "max_conversations_per_day",
          "revoked",
          "revoked_at",
          "last_used_at",
          "created_at",
          "embed_code",
          "kind"
        ],
        "additionalProperties": false,
        "description": "A public chat token for the chat widget on a website."
      },
      "ChatTokenList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatToken"
            },
            "description": "The public chat tokens of the project, newest first, also the revoked ones."
          },
          "daily_spend_limit_eur": {
            "type": "number",
            "description": "What the public chat of this project may spend per day in replies, in euro. Default 5; change it in the portal. 0 turns the live widget off."
          }
        },
        "required": [
          "data",
          "daily_spend_limit_eur"
        ],
        "additionalProperties": false
      },
      "SiteInput": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "maxLength": 63,
            "description": "The address: https://<slug>.hypamoon.app. 3 to 40 characters, lowercase letters, digits and hyphens. Leave it out to derive it from name (with a short suffix if that one is taken)."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "A name for your own overview, like the business or the page."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "Where the file lives on the site, like /index.html, /style.css or /img/logo.png. Relative paths (index.html) get a leading /."
                },
                "content": {
                  "type": "string",
                  "description": "The file itself: plain text for html, css, js, json, txt, xml, svg, webmanifest and md; base64 for png, jpg, gif, webp, ico, woff, woff2, pdf, mp4, webm, mp3 and m4a only when there is no URL for it (prefer source_url; a video or audio file via source_url or an upload, never base64). Leave it out for a file with source_url."
                },
                "source_url": {
                  "type": "string",
                  "minLength": 9,
                  "maxLength": 2048,
                  "description": "Instead of content: a public https URL of the file (port 443), for images, fonts, PDFs, video and audio above all. Hypamoon fetches it once (at most 3 redirects, within the size limits of the plan) and checks it like a file sent with content. Its type must match the path (a .jpg path needs a JPEG, a .mp4 path an MP4). Leave encoding out. Use it instead of base64 whenever the file has a URL; never send a video as base64."
                },
                "encoding": {
                  "type": "string",
                  "enum": [
                    "utf8",
                    "base64"
                  ],
                  "description": "utf8 for text, base64 for images, fonts, PDF, video and audio. Leave it out: it follows from the file type."
                },
                "content_type": {
                  "type": "string",
                  "maxLength": 100,
                  "description": "Leave it out: it follows from the extension (.html is text/html). Only for a path without a known extension, and only an allowed type."
                }
              },
              "required": [
                "path"
              ],
              "additionalProperties": false,
              "description": "One file of the site: with content, or with source_url (Hypamoon fetches it)."
            },
            "minItems": 1,
            "description": "All files of the site: complete, ready for a browser, with at least /index.html. At most 50 files, 1 MB per file (also PDF, video and audio) and 5 MB together."
          },
          "password": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 72,
            "description": "Optional. One shared password for the whole site (4 to 72 characters): visitors enter it once per browser, like password protection on a preview. Not a login system: no accounts, no per-visitor access. null or an empty string removes it. Never returned; the site shows password_protected instead."
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "minItems": 1,
            "maxItems": 200,
            "description": "Optional. Files from the project's media library (list_media) to put on the site, without uploading them again: folders like \"portfolio/\" (with subfolders), file ids, or paths like /media/logo.webp. Each file lands at its library path with its 640 version next to it; private originals never go on a site. A file you also send in files wins."
          },
          "message": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional, but always send it: what changed and why, in one short sentence (up to 200 characters), like a commit message: \"New opening hours for the summer\" or \"Spring menu, the user asked for it\". It shows in the version history; without it Hypamoon writes a short summary like \"Changed /index.html\"."
          }
        },
        "required": [
          "name",
          "files"
        ],
        "additionalProperties": false,
        "description": "A new static site."
      },
      "SiteUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "A name for your own overview, like the business or the page."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "Where the file lives on the site, like /index.html, /style.css or /img/logo.png. Relative paths (index.html) get a leading /."
                },
                "content": {
                  "type": "string",
                  "description": "The file itself: plain text for html, css, js, json, txt, xml, svg, webmanifest and md; base64 for png, jpg, gif, webp, ico, woff, woff2, pdf, mp4, webm, mp3 and m4a only when there is no URL for it (prefer source_url; a video or audio file via source_url or an upload, never base64). Leave it out for a file with source_url."
                },
                "source_url": {
                  "type": "string",
                  "minLength": 9,
                  "maxLength": 2048,
                  "description": "Instead of content: a public https URL of the file (port 443), for images, fonts, PDFs, video and audio above all. Hypamoon fetches it once (at most 3 redirects, within the size limits of the plan) and checks it like a file sent with content. Its type must match the path (a .jpg path needs a JPEG, a .mp4 path an MP4). Leave encoding out. Use it instead of base64 whenever the file has a URL; never send a video as base64."
                },
                "encoding": {
                  "type": "string",
                  "enum": [
                    "utf8",
                    "base64"
                  ],
                  "description": "utf8 for text, base64 for images, fonts, PDF, video and audio. Leave it out: it follows from the file type."
                },
                "content_type": {
                  "type": "string",
                  "maxLength": 100,
                  "description": "Leave it out: it follows from the extension (.html is text/html). Only for a path without a known extension, and only an allowed type."
                }
              },
              "required": [
                "path"
              ],
              "additionalProperties": false,
              "description": "One file of the site: with content, or with source_url (Hypamoon fetches it)."
            },
            "description": "Usually with keep_existing: true: only the files you add or change; every other file stays as it is, so never send images that are already on the site again. Without keep_existing, files is the complete new set of files: a file you leave out is removed, and the answer lists it in warnings."
          },
          "keep_existing": {
            "type": "boolean",
            "description": "true: a partial update. Only the files in files are added or replaced, delete_paths removes files, and every other file stays. Needs if_match_version. The limits, the checks and four-eyes approval apply to the whole site as it will be after the update."
          },
          "delete_paths": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "maxItems": 50,
            "description": "Only with keep_existing: true. Paths of existing files to remove, like /old.html. A path that is not on the site is an error."
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "minItems": 1,
            "maxItems": 200,
            "description": "Optional. Files from the project's media library (list_media) to put on the site, without uploading them again: folders like \"portfolio/\" (with subfolders), file ids, or paths like /media/logo.webp. Each file lands at its library path with its 640 version next to it; private originals never go on a site. A file you also send in files wins."
          },
          "if_match_version": {
            "type": "string",
            "maxLength": 64,
            "description": "The version of the site you are changing, from list_sites, get_site or the last deploy (version). Required with keep_existing. If the site changed since, you get 409 version_conflict with current_version and nothing changes: read the site again and redo your change."
          },
          "password": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 72,
            "description": "Optional. One shared password for the whole site (4 to 72 characters): visitors enter it once per browser, like password protection on a preview. Not a login system: no accounts, no per-visitor access. null or an empty string removes it. Never returned; the site shows password_protected instead."
          },
          "message": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional, but always send it: what changed and why, in one short sentence (up to 200 characters), like a commit message: \"New opening hours for the summer\" or \"Spring menu, the user asked for it\". It shows in the version history; without it Hypamoon writes a short summary like \"Changed /index.html\"."
          },
          "preview": {
            "type": "boolean",
            "description": "true: do not put this change live. It becomes a preview version at a secret link (preview.url in the answer, not indexed), and the live site stays as it is. Put it live later with promote_site_version. Only files (with keep_existing as usual), no name or password. Use it for large changes the user should see first."
          },
          "confirm_replace": {
            "type": "boolean",
            "description": "Only after the user agreed. A deploy without keep_existing that removes more than half of the files, makes the site more than half smaller, or rewrites most of /index.html while other files disappear, is held as a preview instead of going live (guardrail in the answer). Send confirm_replace: true only when the user has said yes to replacing the site like that; then it goes live right away."
          }
        },
        "additionalProperties": false,
        "description": "Change a site: a new name, new files, a password (or null to remove it), or a combination. The slug stays the same. The usual way: send only the changed files with keep_existing: true and if_match_version, images preferably as source_url."
      },
      "Site": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the site. Use it to update or delete the site."
          },
          "slug": {
            "type": "string",
            "description": "The first part of the address: https://<slug>.hypamoon.app."
          },
          "name": {
            "type": "string",
            "description": "Your name for the site."
          },
          "url": {
            "type": "string",
            "description": "The public address of the site, live right away."
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SiteFile"
            },
            "description": "The files of the site, sorted by path, without their content."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the site was created."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the site or its files last changed."
          },
          "version": {
            "type": "string",
            "description": "The version that is live now. Changes on every update; every file on the site carries it as its ETag, so you can check that a page shows this version."
          },
          "password_protected": {
            "type": "boolean",
            "description": "true when visitors have to enter the site's password first (set with password on POST /sites or PUT /sites/{id}). The password itself is never returned."
          },
          "test": {
            "type": "boolean",
            "description": "true for a site of a test project. It is public all the same."
          },
          "taken_offline": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "When Hypamoon took the site offline."
              },
              "reason": {
                "type": "string",
                "description": "Why, in the words the owner also received by email."
              }
            },
            "required": [
              "at",
              "reason"
            ],
            "additionalProperties": false,
            "description": "null while the site is live. Set when Hypamoon took the site offline because of abuse: visitors get a page that says it is unavailable, the files are kept, and PUT and DELETE return 403 site_offline. To object, the owner replies to the email or writes to hello@hypamoon.com."
          },
          "warnings": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "const": "files_removed",
                      "description": "A deploy without keep_existing removed files that were on the site."
                    },
                    "message": {
                      "type": "string",
                      "description": "What happened and what to do if it was not intended."
                    },
                    "paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The paths that are gone."
                    }
                  },
                  "required": [
                    "code",
                    "message",
                    "paths"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "const": "build_hint",
                      "description": "Something on the site looks like it works but does not: a form that saves nowhere, localStorage used as a database, a password checked in JavaScript, a button to # or javascript:, a cart without a checkout, test wording on the live project, or a map from a service that needs a key the site does not have. Advice only: the deploy succeeded."
                    },
                    "hint": {
                      "type": "string",
                      "description": "Which hint, like form_no_target, local_storage_db or cart_without_checkout."
                    },
                    "message": {
                      "type": "string",
                      "description": "What does not work, in plain words."
                    },
                    "paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The files the hint is about."
                    },
                    "next_step": {
                      "type": "string",
                      "description": "What to do next, with the tool to use."
                    }
                  },
                  "required": [
                    "code",
                    "hint",
                    "message",
                    "paths",
                    "next_step"
                  ],
                  "additionalProperties": false
                }
              ],
              "discriminator": {
                "propertyName": "code"
              }
            },
            "description": "Only in the answer of a deploy, and only when there is something to tell: files a deploy without keep_existing removed, and build hints. Warnings never block a deploy."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "url",
          "files",
          "created_at",
          "updated_at",
          "version",
          "password_protected",
          "test",
          "taken_offline"
        ],
        "additionalProperties": false,
        "description": "A static site on hypamoon.app."
      },
      "DomainConnectInput": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 3,
            "maxLength": 300,
            "description": "The domain, like example.com."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional. A site of this project (from deploy_site or list_sites) to serve on the domain and on www."
          },
          "method": {
            "type": "string",
            "enum": [
              "nameservers",
              "cname"
            ],
            "default": "nameservers",
            "description": "nameservers (default): the user points the domain's nameservers to the two we return; existing records, mail included, are copied first. cname: fallback when the nameservers cannot change: only www, through a CNAME and a TXT record at the user's DNS provider."
          },
          "dkim_selectors": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,63}$"
            },
            "maxItems": 10,
            "description": "Optional. The DKIM selector(s) of the user's mail provider (like google or selector1), so their DKIM records are copied too."
          },
          "replace_existing": {
            "type": "boolean",
            "description": "Optional, default false. For a subdomain of a domain this project has on Hypamoon's nameservers: if the subdomain still has an A, AAAA or CNAME record pointing elsewhere (like an old host), the call is refused with what is live there now. true replaces those records (only after the user agreed); undo_dns_change puts them back for 30 days."
          }
        },
        "required": [
          "domain"
        ],
        "additionalProperties": false
      },
      "DomainRegisterInput": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 3,
            "maxLength": 300,
            "description": "The domain, like example.com."
          },
          "years": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 1,
            "description": "Registration period in years. Most extensions only allow 1."
          },
          "accept_registry_terms": {
            "type": "boolean",
            "description": "Must be true: the user (the registrant) explicitly accepted the terms of the registry at registry_terms_url from check_domain."
          },
          "max_price_eur": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000,
            "description": "The highest total the user agreed to pay for all years together, as shown by check_domain. Refused if the price is higher."
          },
          "accept_price_eur": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000,
            "description": "Only for a name with a higher registry price (premium true in check_domain): the price_eur for the first year exactly as check_domain showed it and the user confirmed. Without it, or if the price changed, nothing is bought."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional. A site of this project to serve on the domain once it is active."
          }
        },
        "required": [
          "domain",
          "accept_registry_terms",
          "max_price_eur"
        ],
        "additionalProperties": false
      },
      "DomainSiteInput": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The site to serve on the domain, or null for none."
          }
        },
        "required": [
          "site_id"
        ],
        "additionalProperties": false
      },
      "DomainUpdateInput": {
        "type": "object",
        "properties": {
          "hsts": {
            "type": "string",
            "enum": [
              "off",
              "on",
              "on_with_subdomains"
            ],
            "description": "Tells browsers to always open the domain over https (HSTS). off (default): no such instruction. on: for six months. on_with_subdomains: the same, for every subdomain too. Once on, browsers that visited the site remember it for about six months and will only open the domain over https, even if you turn it off again. Only choose on_with_subdomains if every subdomain of the domain (like mail or shop) works over https too. Only possible when the site is live on the domain with a valid certificate."
          },
          "auto_renew": {
            "type": "boolean",
            "description": "Only for a domain bought or transferred through Hypamoon. true (default): Hypamoon renews it for a year before it expires, from the project's balance. false: it is not renewed and expires on its expiry date."
          },
          "primary": {
            "type": "string",
            "enum": [
              "apex",
              "www"
            ],
            "description": "Which address of the domain is the main one. apex (default): example.com, and www.example.com redirects to it. www: www.example.com, and example.com redirects to it. The redirect is permanent (301) and keeps the path and query; canonical links and the sitemap follow the main address. Only for method nameservers."
          }
        },
        "additionalProperties": false
      },
      "DnsRecordInput": {
        "type": "object",
        "properties": {
          "record_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Optional. The id from list_dns_records of the record to change or delete. Without it, a new record is added."
          },
          "type": {
            "type": "string",
            "enum": [
              "A",
              "AAAA",
              "CNAME",
              "MX",
              "TXT",
              "SRV",
              "CAA",
              "NS"
            ],
            "description": "The record type."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 253,
            "description": "The name: @ for the domain itself, or a subdomain like www, mail or _dmarc."
          },
          "content": {
            "type": "string",
            "maxLength": 4096,
            "description": "The value: an IP address, a hostname, or the text of a TXT record. For SRV: 'priority weight port target'. For CAA: 'flags tag \"value\"'."
          },
          "ttl": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400,
            "description": "Seconds; 1 means automatic."
          },
          "priority": {
            "type": "integer",
            "minimum": 0,
            "maximum": 65535,
            "description": "For MX: the priority (lower is preferred)."
          },
          "proxied": {
            "type": "boolean",
            "description": "For A, AAAA and CNAME: through the CDN (true) or DNS only (false, default)."
          },
          "delete": {
            "type": "boolean",
            "description": "true deletes the record with record_id."
          },
          "confirm_overwrite": {
            "type": "boolean",
            "description": "Optional, default false. A change that replaces or deletes a record that is live now (on an active domain) is refused first with a preflight: what changes, what is live there now, and whether mail is affected. Show it to the user; only after they agree, call again with true. undo_dns_change reverses it for 30 days."
          }
        },
        "required": [
          "type",
          "name"
        ],
        "additionalProperties": false
      },
      "DeployApproval": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "deploy_approval"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the request; pass it as approval_id to approve_deploy or reject_deploy."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "approved",
              "rejected",
              "failed",
              "superseded",
              "expired"
            ],
            "description": "pending until another owner or builder decides. superseded: a newer deploy of the same site replaced it. expired: after 7 days."
          },
          "kind": {
            "type": "string",
            "enum": [
              "new_site",
              "site_update",
              "site_delete",
              "app_deploy",
              "app_rollback"
            ],
            "description": "app_deploy and app_rollback: server code of an app (deploy_app, rollback_app); files and bytes are its modules."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address of the site. For an update or deletion it still shows the live version until approval."
          },
          "files": {
            "type": "integer"
          },
          "bytes": {
            "type": "integer"
          },
          "password_change": {
            "type": "string",
            "enum": [
              "none",
              "remove",
              "set"
            ]
          },
          "requested_by": {
            "type": "object",
            "properties": {
              "user_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "via": {
                "type": "string"
              },
              "app": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "user_id",
              "via",
              "app"
            ]
          },
          "requested_at": {
            "type": "string"
          },
          "expires_at": {
            "type": "string"
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "result": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {}
          }
        },
        "required": [
          "object",
          "id",
          "status",
          "kind",
          "site_id",
          "name",
          "slug",
          "url",
          "files",
          "bytes",
          "password_change",
          "requested_by",
          "requested_at",
          "expires_at",
          "reviewed_by",
          "reviewed_at",
          "reason",
          "result"
        ],
        "description": "Four-eyes approval is on for this project: the deploy waits until another owner or builder approves it in the portal (or with approve_deploy). Nothing changed on the live site yet."
      },
      "ZoneFileInput": {
        "type": "object",
        "properties": {
          "zone_file": {
            "type": "string",
            "minLength": 1,
            "maxLength": 262144,
            "description": "The zone file in BIND format, as exported from the current DNS provider (often called 'export zone' or 'download zone file'). At most 256 KB."
          },
          "replace_existing": {
            "type": "boolean",
            "description": "Optional, default false. true: for every name and type in the file, records here that are not in the file are deleted. false: records are only added."
          }
        },
        "required": [
          "zone_file"
        ],
        "additionalProperties": false
      },
      "DnsRestoreInput": {
        "type": "object",
        "properties": {
          "snapshot_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. The id of a restore point from dns_snapshots in get_domain. Without it: the newest restore point of the previous DNS (a zone file, or the scan when the domain was connected)."
          }
        },
        "additionalProperties": false
      },
      "DomainTransferInput": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 3,
            "maxLength": 300,
            "description": "The domain to move to Hypamoon, like example.com."
          },
          "authcode": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The transfer code (auth code, EPP code; for .nl the token) the user got from their current registrar. Required for most extensions. Never stored."
          },
          "accept_registry_terms": {
            "type": "boolean",
            "description": "Must be true: the user (the registrant) explicitly accepted the terms of the registry at registry_terms_url from check_domain."
          },
          "max_price_eur": {
            "type": "number",
            "minimum": 0,
            "maximum": 10000,
            "description": "The highest amount the user agreed to pay for the transfer. Refused if the price is higher."
          },
          "dns": {
            "type": "string",
            "enum": [
              "hypamoon",
              "keep"
            ],
            "default": "hypamoon",
            "description": "hypamoon (default): the nameservers switch to the DNS zone here when the transfer completes; run preflight_domain first. keep: the nameservers stay as they are; switch later with switch_domain_nameservers."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional. A site of this project to serve on the domain once it is active."
          },
          "dkim_selectors": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,63}$"
            },
            "maxItems": 10,
            "description": "Optional. The DKIM selector(s) of the user's mail provider, so their DKIM records are copied too."
          },
          "ignore_preflight": {
            "type": "boolean",
            "description": "Optional, default false. With dns hypamoon the nameservers switch when the transfer completes, so the transfer is refused (confirmation_required, nothing charged) while preflight_domain is not ready. true transfers anyway, only when the user accepts the listed differences."
          }
        },
        "required": [
          "domain",
          "accept_registry_terms",
          "max_price_eur"
        ],
        "additionalProperties": false
      },
      "DomainNameserversInput": {
        "type": "object",
        "properties": {
          "use": {
            "type": "string",
            "enum": [
              "hypamoon",
              "previous"
            ],
            "description": "hypamoon: the nameservers of the DNS zone here. previous: back to the nameservers the domain had before (to undo the move)."
          },
          "ignore_preflight": {
            "type": "boolean",
            "description": "Optional, default false. With use hypamoon, the switch is refused while preflight_domain is not ready; true switches anyway (only when the user accepts the listed differences)."
          }
        },
        "required": [
          "use"
        ],
        "additionalProperties": false
      },
      "SiteImportInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 8,
            "maxLength": 2000,
            "description": "The https address of the existing site, on a domain this project connected (like https://www.example.com)."
          },
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. An existing site of this project to replace with the imported files. Without it, a new site is created."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Optional. A name for the new site; default the host name."
          },
          "slug": {
            "type": "string",
            "maxLength": 63,
            "description": "Optional. The address of a new site, like deploy_site's slug."
          },
          "max_pages": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Optional. At most this many pages (default 25, at most 50)."
          }
        },
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "WordpressInspectInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 8,
            "maxLength": 2000,
            "description": "The https address of the WordPress site, like https://www.example.com. Any public site: nothing is changed and no login is used."
          }
        },
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "WordpressImportInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 8,
            "maxLength": 2000,
            "description": "The https address of the WordPress site, on a domain this project has proven it owns (like https://www.example.com)."
          },
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. An existing site of this project to put the import in. Without it, a new site is created."
          },
          "keep_existing": {
            "type": "boolean",
            "description": "Only with site_id. true: a partial update. The imported files are added or replaced and every other file of the site stays; images already on the site are not fetched again. Needs if_match_version."
          },
          "if_match_version": {
            "type": "string",
            "maxLength": 64,
            "description": "With keep_existing: the version of the site from get_site or list_sites. If the site changed since, nothing changes (409)."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Optional. A name for the new site; default the site's own name."
          },
          "slug": {
            "type": "string",
            "maxLength": 63,
            "description": "Optional. The address of a new site, like deploy_site's slug."
          },
          "max_posts": {
            "type": "integer",
            "minimum": 0,
            "maximum": 500,
            "description": "Optional. Read at most this many posts, newest first (default all, at most 500)."
          }
        },
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "SiteUploadInput": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. The site the files are for, when you change an existing one (from deploy_site or list_sites). Leave it out for a new site."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200,
                  "description": "Where the file will live on the site, like /img/hero.jpg."
                },
                "sha256": {
                  "type": "string",
                  "maxLength": 64,
                  "description": "The SHA-256 of the file's bytes, as 64 lowercase hex characters (sha256sum, shasum -a 256)."
                },
                "bytes": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "The exact size of the file in bytes."
                },
                "content_type": {
                  "type": "string",
                  "maxLength": 100,
                  "description": "Leave it out: it follows from the extension. Only for a path without a known extension, and only an allowed type."
                }
              },
              "required": [
                "path",
                "sha256",
                "bytes"
              ],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 200,
            "description": "The files to upload, at most 200 per request."
          }
        },
        "required": [
          "files"
        ],
        "additionalProperties": false,
        "description": "Ask for upload links for files that are too large to send inline."
      },
      "SiteUploadList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "The path, normalized (with a leading /)."
                },
                "sha256": {
                  "type": "string",
                  "description": "The SHA-256 you sent, lowercase."
                },
                "bytes": {
                  "type": "integer",
                  "description": "The size you sent."
                },
                "content_type": {
                  "type": "string",
                  "description": "The type the file will be served with."
                },
                "upload_url": {
                  "type": "string",
                  "description": "PUT the exact bytes of the file here (any Content-Type), within expires_at. Using it again with the same bytes changes nothing."
                },
                "method": {
                  "type": "string",
                  "const": "PUT",
                  "description": "Always PUT."
                },
                "expires_at": {
                  "type": "string",
                  "description": "When the link stops working (15 minutes)."
                }
              },
              "required": [
                "path",
                "sha256",
                "bytes",
                "content_type",
                "upload_url",
                "method",
                "expires_at"
              ],
              "additionalProperties": false
            },
            "description": "One upload link per file, in the order you sent them."
          },
          "hint": {
            "type": "string",
            "description": "What to do when the PUT to upload_url fails from your environment (a sandbox may block it)."
          },
          "limits": {
            "type": "object",
            "properties": {
              "files": {
                "type": "integer",
                "description": "Files per site for this project."
              },
              "bytes_per_file": {
                "type": "integer",
                "description": "Bytes per file for this project."
              },
              "bytes_per_site": {
                "type": "integer",
                "description": "Bytes per site for this project, all files together."
              },
              "bytes_per_pdf": {
                "type": "integer",
                "description": "Bytes per PDF for this project."
              },
              "bytes_per_media": {
                "type": "integer",
                "description": "Bytes per video or audio file (mp4, webm, mp3, m4a) for this project, when uploaded with an upload link."
              }
            },
            "required": [
              "files",
              "bytes_per_file",
              "bytes_per_site",
              "bytes_per_pdf",
              "bytes_per_media"
            ],
            "additionalProperties": false,
            "description": "The site limits of this project's plan."
          }
        },
        "required": [
          "data",
          "hint",
          "limits"
        ],
        "additionalProperties": false,
        "description": "Upload links for large files."
      },
      "SiteDropInput": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. The site the files are for (from deploy_site or list_sites). Leave it out for a new site."
          },
          "strip": {
            "type": "string",
            "enum": [
              "location",
              "all",
              "none"
            ],
            "description": "Optional. What the upload page removes, in the user's browser before upload, from photos (JPEG, PNG, WebP) it keeps as they are: originals the user chooses to keep, and photos that would not get smaller as WebP. location (default) removes only the GPS location and keeps camera, date, orientation and copyright; all removes all metadata (the orientation stays so photos stay upright); none keeps the files as they are. Web versions are re-encoded and carry no metadata at all. Use all or none only when the user asks for it."
          },
          "folder": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Optional. The folder under /media/ where the files land, like portfolio or menu/2026. Folders the user drags keep their structure inside it: /media/portfolio/project-12/03.webp. Leave it out to use the user's own folder names."
          },
          "as_is": {
            "type": "boolean",
            "description": "Optional. For moving an existing site (like WordPress uploads): true keeps every file exactly as it is, with no web versions, the same name and extension, and the exact folder path the user drags, under /media/<folder>/."
          }
        },
        "additionalProperties": false,
        "description": "Create a drop link: a page where the user uploads files and whole folders in a browser."
      },
      "SiteDrop": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the drop link; pass it to get_drop (GET /sites/drops/{id})."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "done",
              "expired"
            ],
            "description": "open: waiting for the user. done: the user uploaded the files. expired: 24 hours passed; create a new one."
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The site the link is for, or null."
          },
          "created_at": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "description": "When the link stops working (24 hours after it was made)."
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the user finished uploading, or null."
          },
          "images_only": {
            "type": "boolean",
            "description": "True when the link only takes images (an editor made it)."
          },
          "as_is": {
            "type": "boolean",
            "description": "True when files are kept exactly as they are (for moving a site): no web versions, same names, and paths that follow the user's folders exactly."
          },
          "strip": {
            "type": "string",
            "enum": [
              "location",
              "all",
              "none"
            ],
            "description": "What the upload page removes from photos before upload: location (the default, only the GPS location), all (all metadata) or none. Web versions are re-encoded and carry no metadata at all."
          },
          "folder": {
            "type": [
              "string",
              "null"
            ],
            "description": "The folder under /media/ the files land in, or null for the user's own folder names."
          },
          "max_files": {
            "type": "integer",
            "description": "How many files the user can add with this link (a photo with its versions counts once)."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Where the file lives on the site, like /media/portfolio/project-12/03.webp. You can use another path in deploy_site; keep sha256 and bytes."
                },
                "sha256": {
                  "type": "string",
                  "description": "The SHA-256 of the file."
                },
                "bytes": {
                  "type": "integer",
                  "description": "The size in bytes."
                },
                "content_type": {
                  "type": "string",
                  "description": "The type the file will be served with."
                },
                "name": {
                  "type": "string",
                  "description": "The file name on the user's computer."
                },
                "variant": {
                  "type": "string",
                  "enum": [
                    "2048",
                    "640"
                  ],
                  "description": "For photos made web-ready in the browser: 2048 is the web version (longest side at most 2048 px), 640 the small version of the same photo (path ends in -640). Use the small one in srcset or for thumbnails."
                },
                "width": {
                  "type": "integer",
                  "description": "Width in pixels, for photos."
                },
                "height": {
                  "type": "integer",
                  "description": "Height in pixels, for photos."
                }
              },
              "required": [
                "path",
                "sha256",
                "bytes",
                "content_type"
              ],
              "additionalProperties": false
            },
            "description": "The files that arrived, in order. Private originals are not listed."
          },
          "originals_kept": {
            "type": "integer",
            "description": "How many originals were kept. They are private: never on a site, only downloadable in the portal."
          },
          "site_version": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "version": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "waiting": {
                "type": "boolean"
              },
              "files": {
                "type": "integer"
              }
            },
            "required": [
              "version_id",
              "version",
              "waiting",
              "files"
            ],
            "additionalProperties": false,
            "description": "With site_id, after the user pressed Done: the new version of the site with these files (waiting: it waits for four-eyes approval). The files are already on the site; send only HTML with keep_existing."
          },
          "deploy_files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "sha256": {
                  "type": "string"
                },
                "bytes": {
                  "type": "integer"
                }
              },
              "required": [
                "path",
                "sha256",
                "bytes"
              ],
              "additionalProperties": false
            },
            "description": "The same files in the form deploy_site takes: add them to files, without content."
          },
          "url": {
            "type": "string",
            "description": "Only when the link is created: the page to give the user. It is not shown again."
          },
          "user_message": {
            "type": "string",
            "description": "A sentence to give the user."
          },
          "next_step": {
            "type": "string",
            "description": "What to do next."
          }
        },
        "required": [
          "id",
          "status",
          "site_id",
          "created_at",
          "expires_at",
          "completed_at",
          "images_only",
          "strip",
          "folder",
          "max_files",
          "files",
          "originals_kept",
          "site_version",
          "deploy_files"
        ],
        "additionalProperties": false,
        "description": "A drop link and the files the user uploaded with it."
      },
      "MediaFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the file in the library."
          },
          "path": {
            "type": "string",
            "description": "Where the file goes on a site when you add it with deploy_site media, like /media/portfolio/project-12/03.webp."
          },
          "folder": {
            "type": "string",
            "description": "The folder under /media/, empty for /media/ itself."
          },
          "name": {
            "type": "string",
            "description": "The file name in the library."
          },
          "original_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name on the user's computer, when it differs."
          },
          "content_type": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "alt": {
            "type": [
              "string",
              "null"
            ],
            "description": "The alt text, set with update_media."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the file is served, on the first site that has it at this path. null: it is on no site yet; it is private until you add it with deploy_site media."
          },
          "variants": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "bytes": {
                  "type": "integer"
                },
                "width": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "height": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Where the small version is served, once it is on a site."
                }
              },
              "required": [
                "path",
                "bytes",
                "width",
                "height",
                "url"
              ],
              "additionalProperties": false
            },
            "description": "For photos: 640, the small version for srcset and thumbnails."
          },
          "srcset": {
            "type": [
              "string",
              "null"
            ],
            "description": "A ready srcset for an img tag, once both versions are on a site."
          },
          "original_kept": {
            "type": "boolean",
            "description": "The full-size original is kept privately. It never goes on a site; the user downloads it in the portal."
          },
          "sites": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "site_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                }
              },
              "required": [
                "site_id",
                "name",
                "path",
                "url"
              ],
              "additionalProperties": false
            },
            "description": "The sites that use this file right now, with the path and url there."
          },
          "source": {
            "type": "string",
            "enum": [
              "upload",
              "portaal",
              "site",
              "import"
            ],
            "description": "upload: an upload link; portaal: the portal; site: it was already on a site; import: an import."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "path",
          "folder",
          "name",
          "original_name",
          "content_type",
          "bytes",
          "width",
          "height",
          "alt",
          "url",
          "variants",
          "srcset",
          "original_kept",
          "sites",
          "source",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "description": "A file in the project's media library."
      },
      "MediaList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MediaFile"
            }
          },
          "folders": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "folder": {
                  "type": "string"
                },
                "files": {
                  "type": "integer"
                }
              },
              "required": [
                "folder",
                "files"
              ],
              "additionalProperties": false
            },
            "description": "Every folder in the library with its number of files (empty is /media/ itself)."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as cursor for the next page; null when there is no more."
          }
        },
        "required": [
          "data",
          "folders",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "MediaUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "A new file name. The extension always stays that of the file."
          },
          "folder": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Move the file to this folder under /media/; null or an empty string for /media/ itself."
          },
          "alt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "The alt text for the image; null removes it."
          }
        },
        "additionalProperties": false,
        "description": "Rename or move a file in the library, or set its alt text. Sites keep the file under the path they have until you deploy again."
      },
      "MediaLinkInput": {
        "type": "object",
        "properties": {
          "folder": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Optional. The folder under /media/ where the files land, like portfolio or menu/2026. Folders the user drags keep their structure inside it: /media/portfolio/project-12/03.webp. Leave it out to use the user's own folder names."
          },
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. With a site, Done also puts the files on that site as a new version."
          },
          "images_only": {
            "type": "boolean",
            "description": "Optional. true: the page only takes images."
          },
          "strip": {
            "type": "string",
            "enum": [
              "location",
              "all",
              "none"
            ],
            "description": "Optional. What the upload page removes, in the user's browser before upload, from photos (JPEG, PNG, WebP) it keeps as they are: originals the user chooses to keep, and photos that would not get smaller as WebP. location (default) removes only the GPS location and keeps camera, date, orientation and copyright; all removes all metadata (the orientation stays so photos stay upright); none keeps the files as they are. Web versions are re-encoded and carry no metadata at all. Use all or none only when the user asks for it."
          }
        },
        "additionalProperties": false,
        "description": "Create an upload page for the media library."
      },
      "SiteFile": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "The path of the file on the site, like /index.html."
          },
          "content_type": {
            "type": "string",
            "description": "The type it is served with, like text/html."
          },
          "bytes": {
            "type": "integer",
            "description": "The size of the file in bytes."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the file was last written."
          }
        },
        "required": [
          "path",
          "content_type",
          "bytes",
          "updated_at"
        ],
        "additionalProperties": false,
        "description": "One file of a site, without its content."
      },
      "SiteList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Site"
            },
            "description": "The sites of the project, newest first. Deleted sites are not listed."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "DeletedSite": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the site. Send it to POST /sites/{id}/restore."
          },
          "slug": {
            "type": "string",
            "description": "The address it had and will get back: https://<slug>.hypamoon.app. It stays reserved for this project."
          },
          "name": {
            "type": "string",
            "description": "Your name for the site."
          },
          "deleted_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the site was deleted."
          },
          "restore_until": {
            "type": "string",
            "format": "date-time",
            "description": "Until when the site can be restored. After that its files are erased for good."
          },
          "test": {
            "type": "boolean",
            "description": "true for a site of a test project."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "deleted_at",
          "restore_until",
          "test"
        ],
        "additionalProperties": false,
        "description": "A deleted site that can still be restored."
      },
      "DeletedSiteList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeletedSite"
            },
            "description": "The deleted sites that can still be restored, most recently deleted first."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "RestoredSite": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the site. Use it to update or delete the site."
          },
          "slug": {
            "type": "string",
            "description": "The first part of the address: https://<slug>.hypamoon.app."
          },
          "name": {
            "type": "string",
            "description": "Your name for the site."
          },
          "url": {
            "type": "string",
            "description": "The public address of the site, live right away."
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SiteFile"
            },
            "description": "The files of the site, sorted by path, without their content."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the site was created."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the site or its files last changed."
          },
          "version": {
            "type": "string",
            "description": "The version that is live now. Changes on every update; every file on the site carries it as its ETag, so you can check that a page shows this version."
          },
          "password_protected": {
            "type": "boolean",
            "description": "true when visitors have to enter the site's password first (set with password on POST /sites or PUT /sites/{id}). The password itself is never returned."
          },
          "test": {
            "type": "boolean",
            "description": "true for a site of a test project. It is public all the same."
          },
          "taken_offline": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "When Hypamoon took the site offline."
              },
              "reason": {
                "type": "string",
                "description": "Why, in the words the owner also received by email."
              }
            },
            "required": [
              "at",
              "reason"
            ],
            "additionalProperties": false,
            "description": "null while the site is live. Set when Hypamoon took the site offline because of abuse: visitors get a page that says it is unavailable, the files are kept, and PUT and DELETE return 403 site_offline. To object, the owner replies to the email or writes to hello@hypamoon.com."
          },
          "warnings": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "const": "files_removed",
                      "description": "A deploy without keep_existing removed files that were on the site."
                    },
                    "message": {
                      "type": "string",
                      "description": "What happened and what to do if it was not intended."
                    },
                    "paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The paths that are gone."
                    }
                  },
                  "required": [
                    "code",
                    "message",
                    "paths"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "const": "build_hint",
                      "description": "Something on the site looks like it works but does not: a form that saves nowhere, localStorage used as a database, a password checked in JavaScript, a button to # or javascript:, a cart without a checkout, test wording on the live project, or a map from a service that needs a key the site does not have. Advice only: the deploy succeeded."
                    },
                    "hint": {
                      "type": "string",
                      "description": "Which hint, like form_no_target, local_storage_db or cart_without_checkout."
                    },
                    "message": {
                      "type": "string",
                      "description": "What does not work, in plain words."
                    },
                    "paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The files the hint is about."
                    },
                    "next_step": {
                      "type": "string",
                      "description": "What to do next, with the tool to use."
                    }
                  },
                  "required": [
                    "code",
                    "hint",
                    "message",
                    "paths",
                    "next_step"
                  ],
                  "additionalProperties": false
                }
              ],
              "discriminator": {
                "propertyName": "code"
              }
            },
            "description": "Only in the answer of a deploy, and only when there is something to tell: files a deploy without keep_existing removed, and build hints. Warnings never block a deploy."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "unlisted"
            ],
            "description": "public or unlisted, as before the site was deleted."
          },
          "restore_notes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What did not come back with the site (an app on it, a custom domain), and how to set it up again."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "url",
          "files",
          "created_at",
          "updated_at",
          "version",
          "password_protected",
          "test",
          "taken_offline",
          "restore_notes"
        ],
        "additionalProperties": false,
        "description": "The restored site, live again at its old address."
      },
      "CollectionInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]{1,39}$",
            "description": "The name of the collection, unique in the project: 2 to 40 characters, a lowercase letter first, then lowercase letters, digits and underscores (contact, signups, menu). A form posts to it by this name."
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "What the collection is for, for your own overview."
          },
          "access": {
            "type": "object",
            "properties": {
              "public_write": {
                "type": "boolean",
                "description": "Anyone with the project's public token may add documents from an allowed origin, after a bot check and within limits: a form on a website. Default false."
              },
              "public_read": {
                "type": "boolean",
                "description": "Anyone with the project's public token may read the newest documents (data and created_at only) from an allowed origin. Default false."
              },
              "assistant_read": {
                "type": "boolean",
                "description": "The assistant gets the newest documents (added through the API, never those from a form) in its chat instructions, 8 kB at most. Default false."
              }
            },
            "additionalProperties": false,
            "description": "Who may use the collection besides your API key. Leave it out for private: all false."
          },
          "triggers": {
            "type": "object",
            "properties": {
              "email_on_insert": {
                "type": "boolean",
                "description": "Email every new document to the project's billing email address (live projects only, not in test mode). Default false."
              },
              "webhook_on_insert": {
                "type": "boolean",
                "description": "Send the webhook document.created for every new document, to endpoints subscribed to it. Default false."
              }
            },
            "additionalProperties": false,
            "description": "What happens when a document is added, through the API or a form."
          },
          "email_routing": {
            "type": "object",
            "properties": {
              "to": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 254,
                  "description": "An email address."
                },
                "maxItems": 5,
                "description": "Where the email goes when no rule matches. Empty or left out: the billing email address."
              },
              "rules": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "description": "A top-level field of the document, like vacancy or subject."
                    },
                    "equals": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 200
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ],
                      "description": "The value that field must have. Text matches without regard to case or surrounding spaces; for a list (checkboxes) one of its values must match."
                    },
                    "to": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "maxLength": 254,
                        "description": "An email address."
                      },
                      "minItems": 1,
                      "maxItems": 5,
                      "description": "Where the email goes when this rule matches."
                    }
                  },
                  "required": [
                    "field",
                    "equals",
                    "to"
                  ],
                  "additionalProperties": false
                },
                "maxItems": 10,
                "description": "Up to 10 rules on a field of the new document; the first that matches decides where the email goes."
              }
            },
            "additionalProperties": false,
            "description": "Where the email of email_on_insert goes, instead of the billing email address: at most 5 different addresses in to and rules together. An address must be confirmed before it gets anything: the billing email, the email of a project member and an address on a domain connected to the project are confirmed right away; any other address gets one email with a link to confirm (live projects only). When none of the chosen addresses is confirmed, the email goes to the billing email address. Reply-To stays the email field of the entry."
          },
          "schema": {
            "type": "object",
            "additionalProperties": {},
            "description": "Optional JSON Schema (a subset of draft 2020-12, at most 16 kB) that every document must match, with type \"object\" at the root. Supported: type, properties, required, additionalProperties, items, minItems, maxItems, enum, const, minLength, maxLength, pattern, format (email, uri, date, date-time, time), minimum, maximum, exclusiveMinimum, exclusiveMaximum, title and description."
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "description": "A new collection."
      },
      "CollectionUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]{1,39}$",
            "description": "The name of the collection, unique in the project: 2 to 40 characters, a lowercase letter first, then lowercase letters, digits and underscores (contact, signups, menu). A form posts to it by this name."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "What the collection is for, for your own overview."
          },
          "access": {
            "type": "object",
            "properties": {
              "public_write": {
                "type": "boolean",
                "description": "Anyone with the project's public token may add documents from an allowed origin, after a bot check and within limits: a form on a website. Default false."
              },
              "public_read": {
                "type": "boolean",
                "description": "Anyone with the project's public token may read the newest documents (data and created_at only) from an allowed origin. Default false."
              },
              "assistant_read": {
                "type": "boolean",
                "description": "The assistant gets the newest documents (added through the API, never those from a form) in its chat instructions, 8 kB at most. Default false."
              }
            },
            "additionalProperties": false,
            "description": "Who may use the collection besides your API key. Leave it out for private: all false."
          },
          "triggers": {
            "type": "object",
            "properties": {
              "email_on_insert": {
                "type": "boolean",
                "description": "Email every new document to the project's billing email address (live projects only, not in test mode). Default false."
              },
              "webhook_on_insert": {
                "type": "boolean",
                "description": "Send the webhook document.created for every new document, to endpoints subscribed to it. Default false."
              }
            },
            "additionalProperties": false,
            "description": "What happens when a document is added, through the API or a form."
          },
          "email_routing": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "to": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 254,
                  "description": "An email address."
                },
                "maxItems": 5,
                "description": "Where the email goes when no rule matches. Empty or left out: the billing email address."
              },
              "rules": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "description": "A top-level field of the document, like vacancy or subject."
                    },
                    "equals": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 200
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ],
                      "description": "The value that field must have. Text matches without regard to case or surrounding spaces; for a list (checkboxes) one of its values must match."
                    },
                    "to": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "maxLength": 254,
                        "description": "An email address."
                      },
                      "minItems": 1,
                      "maxItems": 5,
                      "description": "Where the email goes when this rule matches."
                    }
                  },
                  "required": [
                    "field",
                    "equals",
                    "to"
                  ],
                  "additionalProperties": false
                },
                "maxItems": 10,
                "description": "Up to 10 rules on a field of the new document; the first that matches decides where the email goes."
              }
            },
            "additionalProperties": false,
            "description": "Where the email of email_on_insert goes, instead of the billing email address: at most 5 different addresses in to and rules together. An address must be confirmed before it gets anything: the billing email, the email of a project member and an address on a domain connected to the project are confirmed right away; any other address gets one email with a link to confirm (live projects only). When none of the chosen addresses is confirmed, the email goes to the billing email address. Reply-To stays the email field of the entry."
          },
          "schema": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {},
            "description": "Optional JSON Schema (a subset of draft 2020-12, at most 16 kB) that every document must match, with type \"object\" at the root. Supported: type, properties, required, additionalProperties, items, minItems, maxItems, enum, const, minLength, maxLength, pattern, format (email, uri, date, date-time, time), minimum, maximum, exclusiveMinimum, exclusiveMaximum, title and description."
          }
        },
        "additionalProperties": false,
        "description": "Change a collection. Fields you leave out stay as they are."
      },
      "Collection": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the collection. Its name works in the path as well."
          },
          "name": {
            "type": "string",
            "description": "The name, unique in the project. A form posts to it by this name."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the collection is for, or null."
          },
          "access": {
            "type": "object",
            "properties": {
              "public_write": {
                "type": "boolean",
                "description": "A form on an allowed origin may add documents, with the public token, a bot check and limits."
              },
              "public_read": {
                "type": "boolean",
                "description": "A page on an allowed origin may read the newest documents (data and created_at) with the public token."
              },
              "assistant_read": {
                "type": "boolean",
                "description": "The assistant gets the newest documents added through the API in its chat instructions."
              }
            },
            "required": [
              "public_write",
              "public_read",
              "assistant_read"
            ],
            "additionalProperties": false,
            "description": "Who may use the collection besides your API key. All false is private."
          },
          "triggers": {
            "type": "object",
            "properties": {
              "email_on_insert": {
                "type": "boolean",
                "description": "Every new document is emailed to the project's billing email address (live only)."
              },
              "webhook_on_insert": {
                "type": "boolean",
                "description": "Every new document sends the webhook document.created."
              }
            },
            "required": [
              "email_on_insert",
              "webhook_on_insert"
            ],
            "additionalProperties": false
          },
          "email_routing": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "to": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Where the email goes when no rule matches. Empty: the billing email address."
              },
              "rules": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "description": "A top-level field of the new document."
                    },
                    "equals": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ],
                      "description": "The value that field must have."
                    },
                    "to": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Where the email goes when this rule matches."
                    }
                  },
                  "required": [
                    "field",
                    "equals",
                    "to"
                  ],
                  "additionalProperties": false
                },
                "description": "The first rule that matches decides where the email goes."
              },
              "recipients": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "type": "string",
                      "description": "The address, in lowercase."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending"
                      ],
                      "description": "pending: it gets nothing until someone clicks the link in the confirmation email."
                    },
                    "confirmed_by": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "billing_email",
                        "member",
                        "domain",
                        "link",
                        null
                      ],
                      "description": "Why it is confirmed: the billing email, a project member, a domain connected to the project, or the link. null while pending."
                    },
                    "confirmation_sent_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "When the last confirmation email was sent (ISO 8601), or null."
                    }
                  },
                  "required": [
                    "email",
                    "status",
                    "confirmed_by",
                    "confirmation_sent_at"
                  ],
                  "additionalProperties": false
                },
                "description": "Every address in to and rules, with whether it may receive email."
              }
            },
            "required": [
              "to",
              "rules",
              "recipients"
            ],
            "additionalProperties": false,
            "description": "Where the email of email_on_insert goes. null: the billing email address."
          },
          "schema": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {},
            "description": "The JSON Schema every document must match, or null."
          },
          "personal_data": {
            "type": "boolean",
            "description": "true for a collection with personal data imported from WordPress (form entries, orders, customers, users, reviews). It always stays private."
          },
          "document_count": {
            "type": "integer",
            "description": "How many documents the collection has now."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the collection was created."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the collection last changed."
          },
          "test": {
            "type": "boolean",
            "description": "true for a collection of a test project."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "access",
          "triggers",
          "email_routing",
          "schema",
          "document_count",
          "created_at",
          "updated_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "A collection of JSON documents: the data layer of a project."
      },
      "CollectionList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Collection"
            },
            "description": "The collections of the project, newest first. Deleted collections are not listed."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "DocumentInput": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "The document: any JSON object, at most 64 kB."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false,
        "description": "A new document."
      },
      "DocumentUpdate": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "The document: any JSON object, at most 64 kB."
          },
          "merge": {
            "type": "object",
            "additionalProperties": {},
            "description": "Merged into the document as a JSON Merge Patch (RFC 7396): a field you send replaces that field, null removes it, an object is merged field by field."
          }
        },
        "additionalProperties": false,
        "description": "Change a document: send data to replace it, or merge to change some fields. Not both."
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the document."
          },
          "collection_id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of its collection."
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "The document itself: a JSON object."
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "public"
            ],
            "description": "api: added with an API key. public: sent from a form on a website."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the document was added."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the document last changed."
          },
          "test": {
            "type": "boolean",
            "description": "true for a document of a test project. No email was sent for it."
          }
        },
        "required": [
          "id",
          "collection_id",
          "data",
          "source",
          "created_at",
          "updated_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "A document in a collection."
      },
      "DocumentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Document"
            },
            "description": "The documents, newest first."
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether there is another page."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass this as cursor to get the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "additionalProperties": false
      },
      "PublicDocumentInput": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "The fields of the form as a JSON object, like { \"name\": \"Anna\", \"email\": \"anna@example.com\", \"message\": \"...\" }."
          },
          "turnstile_token": {
            "type": "string",
            "maxLength": 2048,
            "description": "The bot-check token from the challenge frame (GET /public/challenge). Required; form.js does this for you."
          },
          "honeypot": {
            "type": "string",
            "maxLength": 1000,
            "description": "The value of a hidden field that people leave empty. When it is filled in, the answer is 201 but nothing is stored."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false,
        "description": "A document from a form on a website."
      },
      "PublicDocumentCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the new document."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was stored."
          },
          "test": {
            "type": "boolean",
            "description": "true when the public token is a test token: stored, but no email is sent."
          }
        },
        "required": [
          "id",
          "created_at",
          "test"
        ],
        "additionalProperties": false,
        "description": "A form entry was stored. The data is not sent back."
      },
      "PublicDocumentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "additionalProperties": {}
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the document was added."
                }
              },
              "required": [
                "data",
                "created_at"
              ],
              "additionalProperties": false
            },
            "description": "The newest documents, newest first: only data and created_at."
          }
        },
        "required": [
          "data"
        ],
        "additionalProperties": false
      },
      "PublicChatConfig": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the project, as the widget shows it."
          },
          "greeting": {
            "type": "string",
            "description": "The fixed greeting, in the language the assistant opens in."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opens in (nl, en, de, fr or es)."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The languages of the assistant."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "live or test, from the token."
          },
          "turnstile": {
            "type": "boolean",
            "description": "Whether POST /public/conversations needs a turnstile_token."
          },
          "max_message_length": {
            "type": "integer",
            "description": "The longest message a visitor can send, in characters."
          },
          "max_turns": {
            "type": "integer",
            "description": "After this many turns the conversation closes."
          }
        },
        "required": [
          "name",
          "greeting",
          "language",
          "languages",
          "mode",
          "turnstile",
          "max_message_length",
          "max_turns"
        ],
        "additionalProperties": false,
        "description": "What the chat widget needs before a conversation starts."
      },
      "PublicConversationInput": {
        "type": "object",
        "properties": {
          "turnstile_token": {
            "type": "string",
            "maxLength": 2048,
            "description": "The bot-check token from the challenge frame. Required when the public token has turnstile on (the default); the widget does this for you."
          }
        },
        "additionalProperties": false,
        "description": "Start a conversation from the chat widget."
      },
      "PublicConversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the conversation."
          },
          "channel": {
            "type": "string",
            "const": "widget",
            "description": "Always widget: started through a public chat token."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ],
            "description": "open: takes messages. closed: takes no more messages."
          },
          "language": {
            "type": "string",
            "description": "The language the assistant opened in."
          },
          "turns": {
            "type": "integer",
            "description": "Visitor messages answered so far."
          },
          "max_turns": {
            "type": "integer",
            "description": "After this many turns the conversation closes (close_reason turn_limit)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the conversation started."
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last message was sent."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the conversation closed."
          },
          "close_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "closed",
              "inactive",
              "turn_limit",
              null
            ],
            "description": "Why it closed: inactive after 30 minutes without a message, or turn_limit."
          },
          "test": {
            "type": "boolean",
            "description": "true for a conversation through a test token (hm_pubtest_)."
          }
        },
        "required": [
          "id",
          "channel",
          "status",
          "language",
          "turns",
          "max_turns",
          "created_at",
          "last_message_at",
          "closed_at",
          "close_reason",
          "test"
        ],
        "additionalProperties": false,
        "description": "A conversation as the widget sees it: only what concerns the visitor. No metadata, customer details or summary; your backend reads those with GET /conversations/{id}."
      },
      "PublicConversationStart": {
        "type": "object",
        "properties": {
          "conversation": {
            "$ref": "#/components/schemas/PublicConversation"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "The fixed greeting, as the first message."
          },
          "conversation_token": {
            "type": "string",
            "description": "The secret of this conversation, for this visitor only. Send it as X-Conversation-Token on every next request about this conversation. Keep it in the page (or sessionStorage), never in a URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When conversation_token stops working (6 hours after the start)."
          }
        },
        "required": [
          "conversation",
          "messages",
          "conversation_token",
          "expires_at"
        ],
        "additionalProperties": false,
        "description": "A new conversation from the widget, with its secret."
      },
      "PublicConversationDetail": {
        "type": "object",
        "properties": {
          "conversation": {
            "$ref": "#/components/schemas/PublicConversation"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "All messages, oldest first, starting with the greeting."
          }
        },
        "required": [
          "conversation",
          "messages"
        ],
        "additionalProperties": false,
        "description": "The visitor's own conversation with its messages."
      },
      "PublicChatTurn": {
        "type": "object",
        "properties": {
          "message": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the message."
              },
              "role": {
                "type": "string",
                "enum": [
                  "customer",
                  "assistant"
                ],
                "description": "Who wrote it: the customer, or the assistant (also the fixed greeting)."
              },
              "text": {
                "type": "string",
                "description": "The text."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the message was stored."
              }
            },
            "required": [
              "id",
              "role",
              "text",
              "created_at"
            ],
            "additionalProperties": false,
            "description": "The visitor's message as stored."
          },
          "reply": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the message."
              },
              "role": {
                "type": "string",
                "enum": [
                  "customer",
                  "assistant"
                ],
                "description": "Who wrote it: the customer, or the assistant (also the fixed greeting)."
              },
              "text": {
                "type": "string",
                "description": "The text."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the message was stored."
              }
            },
            "required": [
              "id",
              "role",
              "text",
              "created_at"
            ],
            "additionalProperties": false,
            "description": "The assistant's reply. Show this text."
          },
          "conversation": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "The id of the conversation."
              },
              "channel": {
                "type": "string",
                "const": "widget",
                "description": "Always widget: started through a public chat token."
              },
              "status": {
                "type": "string",
                "enum": [
                  "open",
                  "closed"
                ],
                "description": "open: takes messages. closed: takes no more messages."
              },
              "language": {
                "type": "string",
                "description": "The language the assistant opened in."
              },
              "turns": {
                "type": "integer",
                "description": "Visitor messages answered so far."
              },
              "max_turns": {
                "type": "integer",
                "description": "After this many turns the conversation closes (close_reason turn_limit)."
              },
              "created_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the conversation started."
              },
              "last_message_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the last message was sent."
              },
              "closed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the conversation closed."
              },
              "close_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "closed",
                  "inactive",
                  "turn_limit",
                  null
                ],
                "description": "Why it closed: inactive after 30 minutes without a message, or turn_limit."
              },
              "test": {
                "type": "boolean",
                "description": "true for a conversation through a test token (hm_pubtest_)."
              }
            },
            "required": [
              "id",
              "channel",
              "status",
              "language",
              "turns",
              "max_turns",
              "created_at",
              "last_message_at",
              "closed_at",
              "close_reason",
              "test"
            ],
            "additionalProperties": false,
            "description": "The conversation after this turn. status closed means the turn limit was reached."
          }
        },
        "required": [
          "message",
          "reply",
          "conversation"
        ],
        "additionalProperties": false,
        "description": "One turn in the widget: the visitor's message and the assistant's reply."
      },
      "Balance": {
        "type": "object",
        "properties": {
          "balance_eur": {
            "type": "number",
            "description": "The prepaid balance in euro, excluding VAT. Can be slightly below zero after a call that ran on."
          },
          "currency": {
            "type": "string",
            "const": "EUR",
            "description": "Always EUR."
          },
          "spent_this_month_eur": {
            "type": "number",
            "description": "What was deducted this calendar month (minus refunds). Counts toward the spending cap."
          },
          "spend_cap_eur": {
            "type": [
              "number",
              "null"
            ],
            "description": "The spending cap per calendar month, or null without one."
          },
          "auto_reload": {
            "$ref": "#/components/schemas/AutoReload"
          },
          "transactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            },
            "description": "The latest 20 transactions, newest first."
          },
          "test": {
            "type": "boolean",
            "description": "true with a test key: you see the balance of the live project, read-only. Test mode is free and never touches the balance."
          }
        },
        "required": [
          "balance_eur",
          "currency",
          "spent_this_month_eur",
          "spend_cap_eur",
          "auto_reload",
          "transactions",
          "test"
        ],
        "additionalProperties": false,
        "description": "The prepaid balance with the latest transactions."
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the transaction."
          },
          "type": {
            "type": "string",
            "enum": [
              "top_up",
              "charge",
              "refund",
              "adjustment"
            ],
            "description": "top_up: money added with a payment. charge: usage deducted. refund: a charge given back (an SMS the carrier refused, for example). adjustment: a correction by Hypamoon support."
          },
          "source": {
            "type": "string",
            "enum": [
              "payment",
              "sms",
              "call",
              "number",
              "chat",
              "site",
              "support"
            ],
            "description": "What caused it: a payment, an SMS, a call, a number, a chat reply of the assistant, site requests above what the plan includes, or Hypamoon support."
          },
          "amount_eur": {
            "type": "number",
            "description": "The amount in euro excluding VAT, with a sign: positive for a top-up or refund, negative for a charge. Up to four decimals."
          },
          "balance_after_eur": {
            "type": "number",
            "description": "The balance right after this transaction, in euro excluding VAT."
          },
          "description": {
            "type": "string",
            "description": "What it was for, in a few words."
          },
          "quantity": {
            "type": [
              "number",
              "null"
            ],
            "description": "How many units, like 2 SMS segments or 3 minutes. Null for a top-up."
          },
          "unit": {
            "type": [
              "string",
              "null"
            ],
            "description": "The unit of quantity, like segment, minute or month. Null for a top-up."
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id of what caused it: a message, a call, a number or a payment."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the transaction was booked."
          }
        },
        "required": [
          "id",
          "type",
          "source",
          "amount_eur",
          "balance_after_eur",
          "description",
          "quantity",
          "unit",
          "reference_id",
          "created_at"
        ],
        "additionalProperties": false,
        "description": "One line in the ledger of the prepaid balance. The same shape in GET /balance and the balance webhooks."
      },
      "AutoReload": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether automatic top-up is switched on. Change it in the portal."
          },
          "active": {
            "type": "boolean",
            "description": "Whether it can run now: switched on, with a saved card that has not expired."
          },
          "threshold_eur": {
            "type": "number",
            "description": "When the balance drops below this amount, a top-up starts."
          },
          "amount_eur": {
            "type": "number",
            "description": "How much each automatic top-up adds, excluding VAT."
          },
          "max_per_month": {
            "type": "integer",
            "description": "The most automatic top-ups per calendar month."
          },
          "used_this_month": {
            "type": "integer",
            "description": "How many automatic top-ups ran this calendar month."
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it is off or cannot run: no_card, card_expired, repeated_failures or turned_off. Null when it is active."
          },
          "card": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The card brand, like Visa or Mastercard."
              },
              "last4": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The last four digits."
              },
              "expiry": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The expiry date of the card."
              }
            },
            "required": [
              "brand",
              "last4",
              "expiry"
            ],
            "additionalProperties": false,
            "description": "The saved card used for automatic top-up, or null without one."
          }
        },
        "required": [
          "enabled",
          "active",
          "threshold_eur",
          "amount_eur",
          "max_per_month",
          "used_this_month",
          "disabled_reason",
          "card"
        ],
        "additionalProperties": false,
        "description": "Automatic top-up with a saved card."
      },
      "Reload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the top-up payment."
          },
          "amount_eur": {
            "type": "number",
            "description": "The amount it tried to add, excluding VAT."
          },
          "status": {
            "type": "string",
            "description": "The status of the payment, like failed, canceled or expired."
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short code for why it failed, or null."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the top-up started."
          }
        },
        "required": [
          "id",
          "amount_eur",
          "status",
          "failure_reason",
          "created_at"
        ],
        "additionalProperties": false,
        "description": "An automatic top-up that failed."
      },
      "BalanceLowEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "balance.low"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "balance_eur": {
                "type": "number",
                "description": "The balance at the moment of the event, in euro excluding VAT."
              },
              "threshold_eur": {
                "type": "number",
                "description": "The threshold for automatic top-up. With balance.low and balance.reload_failed."
              },
              "transaction": {
                "$ref": "#/components/schemas/Transaction",
                "description": "With balance.topped_up: the top-up in the ledger."
              },
              "reload": {
                "$ref": "#/components/schemas/Reload",
                "description": "With balance.reload_failed: the failed top-up."
              }
            },
            "required": [
              "balance_eur"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when a charge takes the balance below the automatic top-up threshold. Carries balance_eur and threshold_eur."
      },
      "BalanceToppedUpEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "balance.topped_up"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "balance_eur": {
                "type": "number",
                "description": "The balance at the moment of the event, in euro excluding VAT."
              },
              "threshold_eur": {
                "type": "number",
                "description": "The threshold for automatic top-up. With balance.low and balance.reload_failed."
              },
              "transaction": {
                "$ref": "#/components/schemas/Transaction",
                "description": "With balance.topped_up: the top-up in the ledger."
              },
              "reload": {
                "$ref": "#/components/schemas/Reload",
                "description": "With balance.reload_failed: the failed top-up."
              }
            },
            "required": [
              "balance_eur"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when a top-up was paid and added to the balance, by hand or automatically. Carries balance_eur and transaction."
      },
      "BalanceReloadFailedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "balance.reload_failed"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "balance_eur": {
                "type": "number",
                "description": "The balance at the moment of the event, in euro excluding VAT."
              },
              "threshold_eur": {
                "type": "number",
                "description": "The threshold for automatic top-up. With balance.low and balance.reload_failed."
              },
              "transaction": {
                "$ref": "#/components/schemas/Transaction",
                "description": "With balance.topped_up: the top-up in the ledger."
              },
              "reload": {
                "$ref": "#/components/schemas/Reload",
                "description": "With balance.reload_failed: the failed top-up."
              }
            },
            "required": [
              "balance_eur"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent when an automatic top-up failed. Carries balance_eur, threshold_eur and reload. After three failures in a row automatic top-up is switched off."
      },
      "DocumentCreatedEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event, evt_ plus 32 hex characters. It stays the same on retries."
          },
          "type": {
            "type": "string",
            "const": "document.created"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened."
          },
          "data": {
            "type": "object",
            "properties": {
              "collection": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ],
                "additionalProperties": false,
                "description": "The collection the document was added to."
              },
              "document": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "The document as it is when the webhook is sent."
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "api",
                      "public"
                    ],
                    "description": "api or public (a form)."
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the document was added."
                  }
                },
                "required": [
                  "id",
                  "data",
                  "source",
                  "created_at"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "collection",
              "document"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "additionalProperties": false,
        "description": "Sent for every new document in a collection with webhook_on_insert, from the API or a form. A document deleted before delivery is not sent."
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "A URL that explains the error and the fix: https://hypamoon.com/docs/errors/<slug>."
          },
          "title": {
            "type": "string",
            "description": "The HTTP status in words."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status."
          },
          "detail": {
            "type": "string",
            "description": "What went wrong in this request."
          },
          "request_id": {
            "type": "string",
            "description": "The id of this request, also in the X-Request-Id header. Mention it when you contact support."
          },
          "user_message": {
            "type": "string",
            "description": "One calm sentence in plain English for the end user, without technical terms. A chat or app can show it as it is."
          },
          "next_step": {
            "type": "string",
            "description": "With some errors: what the chat or agent itself should do next."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            },
            "description": "With validation_failed, schema_mismatch and team_targets_not_allowed: every problem."
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "With company_details_missing: the missing fields, as paths of PUT /project/company."
          },
          "current_version": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ],
            "description": "With version_conflict: the version that is there now (a number on a call plan, a string on a site; send it as if_match_version)."
          },
          "spend_cap_eur": {
            "type": "number",
            "description": "With billing_blocked: the spending cap per calendar month."
          },
          "spent_eur": {
            "type": "number",
            "description": "With billing_blocked: what was spent this calendar month."
          },
          "balance_eur": {
            "type": "number",
            "description": "With insufficient_balance: the prepaid balance now."
          },
          "required_eur": {
            "type": "number",
            "description": "With insufficient_balance: what this request costs."
          },
          "auto_reload_started": {
            "type": "boolean",
            "description": "With insufficient_balance: true when an automatic top-up just started; retry in a minute."
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "request_id",
          "user_message"
        ],
        "description": "An error, as application/problem+json (RFC 9457)."
      },
      "FieldError": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Where the problem is, with dots, like address.postal_code. body means the body as a whole."
          },
          "message": {
            "type": "string",
            "description": "What is wrong, in English."
          }
        },
        "required": [
          "path",
          "message"
        ],
        "additionalProperties": false
      },
      "RecentError": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "at": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "mcp",
              "rest"
            ]
          },
          "client": {
            "type": "string"
          },
          "tool": {
            "type": [
              "string",
              "null"
            ]
          },
          "route": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "request_id": {
            "type": "string"
          },
          "mode": {
            "type": [
              "string",
              "null"
            ]
          },
          "arguments": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {}
          },
          "field_errors": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "message"
              ]
            }
          }
        },
        "required": [
          "id",
          "at",
          "source",
          "client",
          "tool",
          "route",
          "error",
          "status",
          "detail",
          "duration_ms",
          "request_id",
          "mode",
          "arguments",
          "field_errors"
        ]
      },
      "RecentErrorList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecentError"
            }
          },
          "next_before": {
            "type": [
              "string",
              "null"
            ]
          },
          "dropped_today": {
            "type": "integer"
          }
        },
        "required": [
          "data",
          "next_before",
          "dropped_today"
        ]
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hm_live_..., hm_test_..., or an OAuth access token hm_oat_...",
        "description": "Your API key as a bearer token: `Authorization: Bearer hm_live_...`. Live keys start with hm_live_; test keys start with hm_test_, belong to a test project, never reach the carrier and cost nothing. A key has scopes (project:read, project:write, numbers:read, calls:read, numbers:write, assistant:write, webhooks:write, messages:read, messages:write, calls:write, chat:read, chat:write, sites:read, sites:write, data:read, data:write); each operation names the one it needs in x-hypamoon-scope. Keys are shown only once; store them as a secret, never in a frontend. An OAuth access token (hm_oat_..., see OAuth2) is sent the same way and works on every /api/v1 route."
      },
      "WordPressToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hm_wp_...",
        "description": "Only for the /wordpress routes of the Hypamoon Connect plugin for WordPress. The plugin gets this token once, with POST /wordpress/connect and a one-time code, and sends it with X-Hypamoon-Site (the address of the WordPress site). It can only send this site's inventory and the data the user selected, to one project, for 30 days, and it can be revoked. It is not an API key."
      },
      "PublicChatToken": {
        "type": "apiKey",
        "in": "query",
        "name": "token",
        "description": "Only for the /public routes of the chat widget. The public chat token (hm_pub_... or hm_pubtest_...) in the query string, from a browser page on one of the token's allowed origins (the Origin header must match exactly). It is not a secret: it is in the page source of your website. It can only chat, only from those origins, with a bot check and its own limits, and you can revoke it. Create one with POST /chat-tokens or in the portal. Never send your API key from a browser."
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "For assistants that connect by URL (Claude.ai, ChatGPT, Grok). Authorization code with PKCE (S256 only); register a client with dynamic client registration at https://api.hypamoon.com/oauth/register. Send resource=https://api.hypamoon.com/api/v1/mcp. The user picks the project (test or live) and the scopes on the consent screen. The access token (hm_oat_...) works on every /api/v1 route, not only /mcp; refresh it with the refresh token. The user can revoke a connection in the portal under Koppelingen.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.hypamoon.com/oauth/authorize",
            "tokenUrl": "https://api.hypamoon.com/oauth/token",
            "refreshUrl": "https://api.hypamoon.com/oauth/token",
            "scopes": {
              "project:read": "Read the project: its name, whether it is in test mode or live, and what still needs to be done",
              "project:write": "Fill in the project's company or personal details, manage agents and request secrets for other services",
              "numbers:read": "Read the numbers and their call plans",
              "calls:read": "Read calls, with transcript and summary",
              "numbers:write": "Order numbers and change call plans",
              "assistant:write": "Configure the voice AI assistant",
              "webhooks:write": "Create, test and delete webhooks",
              "messages:read": "Read the text messages and emails the project sent, with their status",
              "messages:write": "Send text messages (SMS) and emails on behalf of the project",
              "calls:write": "Let the assistant call people on behalf of the project",
              "chat:read": "Read the chat conversations with the assistant, with messages and summary",
              "chat:write": "Start chat conversations with the assistant and send customer messages",
              "sites:read": "Read the project's sites, apps and domains",
              "sites:write": "Publish sites and apps on <slug>.hypamoon.app, change and delete them, and manage domains",
              "data:read": "Read the project's collections and their documents, and the business processes (brain)",
              "data:write": "Create, change and delete collections and their documents, and the business processes (brain)",
              "clients:read": "As an agency: list your client projects, read their usage and activity, and read inside a client project with the Hypamoon-Project header",
              "clients:write": "As an agency: set up client projects, ask clients for access, invite owners, and build inside a client project with the Hypamoon-Project header"
            }
          }
        }
      }
    },
    "headers": {
      "X-Request-Id": {
        "schema": {
          "type": "string"
        },
        "description": "The id of this request. Include it when you contact support."
      },
      "ETag": {
        "schema": {
          "type": "string"
        },
        "description": "The version of the call plan, like \"3\". Send it back as If-Match."
      },
      "Retry-After": {
        "schema": {
          "type": "integer"
        },
        "description": "Seconds to wait before you try again."
      },
      "WWW-Authenticate": {
        "schema": {
          "type": "string"
        },
        "description": "The OAuth challenge of the MCP server (RFC 6750, RFC 9728): `Bearer resource_metadata=\"<url>\", scope=\"...\"`, with `error=\"invalid_token\"` for an expired or revoked token. An MCP client follows resource_metadata to find the authorization server and start OAuth."
      },
      "Idempotent-Replayed": {
        "schema": {
          "type": "string",
          "const": "true"
        },
        "description": "Present when this is the stored response to an earlier request with the same Idempotency-Key."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Unauthorized: unauthorized, missing_api_key, invalid_api_key, api_key_revoked, project_deleted.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "x-hypamoon-errors": [
          "unauthorized",
          "missing_api_key",
          "invalid_api_key",
          "api_key_revoked",
          "project_deleted"
        ],
        "content": {
          "application/problem+json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Problem"
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "https://hypamoon.com/docs/errors/unauthorized",
                        "https://hypamoon.com/docs/errors/missing_api_key",
                        "https://hypamoon.com/docs/errors/invalid_api_key",
                        "https://hypamoon.com/docs/errors/api_key_revoked",
                        "https://hypamoon.com/docs/errors/project_deleted"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden: project_not_enabled, key_mode_mismatch, missing_scope, verification_required.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "x-hypamoon-errors": [
          "project_not_enabled",
          "key_mode_mismatch",
          "missing_scope",
          "verification_required"
        ],
        "content": {
          "application/problem+json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Problem"
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "https://hypamoon.com/docs/errors/project_not_enabled",
                        "https://hypamoon.com/docs/errors/key_mode_mismatch",
                        "https://hypamoon.com/docs/errors/missing_scope",
                        "https://hypamoon.com/docs/errors/verification_required"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too Many Requests: rate_limited.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "x-hypamoon-errors": [
          "rate_limited"
        ],
        "content": {
          "application/problem+json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Problem"
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "https://hypamoon.com/docs/errors/rate_limited"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal Server Error: internal_error.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "x-hypamoon-errors": [
          "internal_error"
        ],
        "content": {
          "application/problem+json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Problem"
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "https://hypamoon.com/docs/errors/internal_error"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Service Unavailable: service_unavailable.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "x-hypamoon-errors": [
          "service_unavailable"
        ],
        "content": {
          "application/problem+json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Problem"
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "https://hypamoon.com/docs/errors/service_unavailable"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "x-hypamoon-error-docs": "https://hypamoon.com/docs/errors/",
  "x-hypamoon-error-types": [
    {
      "slug": "bad_request",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/bad_request"
    },
    {
      "slug": "unauthorized",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/unauthorized"
    },
    {
      "slug": "payment_required",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/payment_required"
    },
    {
      "slug": "forbidden",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/forbidden"
    },
    {
      "slug": "not_found",
      "status": [
        404
      ],
      "type": "https://hypamoon.com/docs/errors/not_found"
    },
    {
      "slug": "method_not_allowed",
      "status": [
        405
      ],
      "type": "https://hypamoon.com/docs/errors/method_not_allowed"
    },
    {
      "slug": "conflict",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/conflict"
    },
    {
      "slug": "version_conflict",
      "status": [
        409,
        412
      ],
      "type": "https://hypamoon.com/docs/errors/version_conflict"
    },
    {
      "slug": "confirmation_required",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/confirmation_required"
    },
    {
      "slug": "validation_failed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/validation_failed"
    },
    {
      "slug": "rate_limited",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/rate_limited"
    },
    {
      "slug": "internal_error",
      "status": [
        500
      ],
      "type": "https://hypamoon.com/docs/errors/internal_error"
    },
    {
      "slug": "upstream_error",
      "status": [
        502
      ],
      "type": "https://hypamoon.com/docs/errors/upstream_error"
    },
    {
      "slug": "service_unavailable",
      "status": [
        503
      ],
      "type": "https://hypamoon.com/docs/errors/service_unavailable"
    },
    {
      "slug": "invalid_json",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_json"
    },
    {
      "slug": "invalid_parameter",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_parameter"
    },
    {
      "slug": "missing_api_key",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/missing_api_key"
    },
    {
      "slug": "invalid_api_key",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_api_key"
    },
    {
      "slug": "api_key_revoked",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/api_key_revoked"
    },
    {
      "slug": "project_deleted",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/project_deleted"
    },
    {
      "slug": "project_not_enabled",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/project_not_enabled"
    },
    {
      "slug": "missing_scope",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/missing_scope"
    },
    {
      "slug": "invalid_chat_token",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_chat_token"
    },
    {
      "slug": "chat_token_revoked",
      "status": [
        401,
        409
      ],
      "type": "https://hypamoon.com/docs/errors/chat_token_revoked"
    },
    {
      "slug": "origin_not_allowed",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/origin_not_allowed"
    },
    {
      "slug": "turnstile_failed",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/turnstile_failed"
    },
    {
      "slug": "invalid_conversation_token",
      "status": [
        401
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_conversation_token"
    },
    {
      "slug": "public_chat_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/public_chat_limit_reached"
    },
    {
      "slug": "chat_unavailable",
      "status": [
        503
      ],
      "type": "https://hypamoon.com/docs/errors/chat_unavailable"
    },
    {
      "slug": "vat_number_invalid",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/vat_number_invalid"
    },
    {
      "slug": "country_not_supported",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/country_not_supported"
    },
    {
      "slug": "key_mode_mismatch",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/key_mode_mismatch"
    },
    {
      "slug": "test_key_required",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/test_key_required"
    },
    {
      "slug": "test_number_limit_reached",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/test_number_limit_reached"
    },
    {
      "slug": "company_details_missing",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/company_details_missing"
    },
    {
      "slug": "business_required",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/business_required"
    },
    {
      "slug": "account_type_change_blocked",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/account_type_change_blocked"
    },
    {
      "slug": "spend_cap_missing",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/spend_cap_missing"
    },
    {
      "slug": "payment_mandate_missing",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/payment_mandate_missing"
    },
    {
      "slug": "billing_blocked",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/billing_blocked"
    },
    {
      "slug": "insufficient_balance",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/insufficient_balance"
    },
    {
      "slug": "regulatory_documents_missing",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/regulatory_documents_missing"
    },
    {
      "slug": "verification_required",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/verification_required"
    },
    {
      "slug": "number_unavailable",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/number_unavailable"
    },
    {
      "slug": "number_already_yours",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/number_already_yours"
    },
    {
      "slug": "no_numbers_available",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/no_numbers_available"
    },
    {
      "slug": "number_limit_reached",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/number_limit_reached"
    },
    {
      "slug": "order_status_unknown",
      "status": [
        502
      ],
      "type": "https://hypamoon.com/docs/errors/order_status_unknown"
    },
    {
      "slug": "number_release_not_supported",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/number_release_not_supported"
    },
    {
      "slug": "telephony_unavailable",
      "status": [
        503
      ],
      "type": "https://hypamoon.com/docs/errors/telephony_unavailable"
    },
    {
      "slug": "idempotency_key_missing",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/idempotency_key_missing"
    },
    {
      "slug": "idempotency_key_invalid",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/idempotency_key_invalid"
    },
    {
      "slug": "idempotency_key_reused",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/idempotency_key_reused"
    },
    {
      "slug": "idempotency_request_in_progress",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/idempotency_request_in_progress"
    },
    {
      "slug": "assistant_not_configured",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/assistant_not_configured"
    },
    {
      "slug": "voice_ai_not_enabled",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/voice_ai_not_enabled"
    },
    {
      "slug": "team_targets_not_allowed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/team_targets_not_allowed"
    },
    {
      "slug": "forward_target_not_allowed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/forward_target_not_allowed"
    },
    {
      "slug": "invalid_webhook_endpoint",
      "status": [
        400
      ],
      "type": "https://hypamoon.com/docs/errors/invalid_webhook_endpoint"
    },
    {
      "slug": "webhook_limit_reached",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/webhook_limit_reached"
    },
    {
      "slug": "webhook_endpoint_disabled",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/webhook_endpoint_disabled"
    },
    {
      "slug": "destination_not_allowed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/destination_not_allowed"
    },
    {
      "slug": "message_too_long",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/message_too_long"
    },
    {
      "slug": "sender_not_allowed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/sender_not_allowed"
    },
    {
      "slug": "message_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/message_limit_reached"
    },
    {
      "slug": "number_required",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/number_required"
    },
    {
      "slug": "outside_calling_hours",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/outside_calling_hours"
    },
    {
      "slug": "concurrent_call_limit",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/concurrent_call_limit"
    },
    {
      "slug": "call_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/call_limit_reached"
    },
    {
      "slug": "consent_required",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/consent_required"
    },
    {
      "slug": "conversation_closed",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/conversation_closed"
    },
    {
      "slug": "conversation_busy",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/conversation_busy"
    },
    {
      "slug": "conversation_turn_limit_reached",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/conversation_turn_limit_reached"
    },
    {
      "slug": "conversation_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/conversation_limit_reached"
    },
    {
      "slug": "test_free_replies_used",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/test_free_replies_used"
    },
    {
      "slug": "test_free_calls_used",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/test_free_calls_used"
    },
    {
      "slug": "site_limit_reached",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/site_limit_reached"
    },
    {
      "slug": "slug_taken",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/slug_taken"
    },
    {
      "slug": "slug_reserved",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/slug_reserved"
    },
    {
      "slug": "site_too_large",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/site_too_large"
    },
    {
      "slug": "site_blocked",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/site_blocked"
    },
    {
      "slug": "site_offline",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/site_offline"
    },
    {
      "slug": "deploy_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/deploy_limit_reached"
    },
    {
      "slug": "upload_not_received",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/upload_not_received"
    },
    {
      "slug": "phone_verification_required",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/phone_verification_required"
    },
    {
      "slug": "collection_limit_reached",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/collection_limit_reached"
    },
    {
      "slug": "collection_name_taken",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/collection_name_taken"
    },
    {
      "slug": "document_limit_reached",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/document_limit_reached"
    },
    {
      "slug": "document_too_large",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/document_too_large"
    },
    {
      "slug": "schema_mismatch",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/schema_mismatch"
    },
    {
      "slug": "collection_not_public",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/collection_not_public"
    },
    {
      "slug": "plan_limit_reached",
      "status": [
        402,
        409
      ],
      "type": "https://hypamoon.com/docs/errors/plan_limit_reached"
    },
    {
      "slug": "app_blocked",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/app_blocked"
    },
    {
      "slug": "app_too_large",
      "status": [
        413
      ],
      "type": "https://hypamoon.com/docs/errors/app_too_large"
    },
    {
      "slug": "migration_failed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/migration_failed"
    },
    {
      "slug": "file_too_large",
      "status": [
        413
      ],
      "type": "https://hypamoon.com/docs/errors/file_too_large"
    },
    {
      "slug": "query_failed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/query_failed"
    },
    {
      "slug": "mail_not_enabled",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/mail_not_enabled"
    },
    {
      "slug": "email_paused",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/email_paused"
    },
    {
      "slug": "recipient_not_allowed",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/recipient_not_allowed"
    },
    {
      "slug": "email_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/email_limit_reached"
    },
    {
      "slug": "email_blocked",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/email_blocked"
    },
    {
      "slug": "email_domain_not_verified",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/email_domain_not_verified"
    },
    {
      "slug": "attachment_too_large",
      "status": [
        413
      ],
      "type": "https://hypamoon.com/docs/errors/attachment_too_large"
    },
    {
      "slug": "attachment_not_allowed",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/attachment_not_allowed"
    },
    {
      "slug": "email_address_invalid",
      "status": [
        422
      ],
      "type": "https://hypamoon.com/docs/errors/email_address_invalid"
    },
    {
      "slug": "bimi_not_ready",
      "status": [
        409
      ],
      "type": "https://hypamoon.com/docs/errors/bimi_not_ready"
    },
    {
      "slug": "dedicated_ip_not_available",
      "status": [
        403
      ],
      "type": "https://hypamoon.com/docs/errors/dedicated_ip_not_available"
    },
    {
      "slug": "ai_credit_exhausted",
      "status": [
        402
      ],
      "type": "https://hypamoon.com/docs/errors/ai_credit_exhausted"
    },
    {
      "slug": "ai_limit_reached",
      "status": [
        429
      ],
      "type": "https://hypamoon.com/docs/errors/ai_limit_reached"
    }
  ],
  "x-hypamoon-pricing": {
    "version": "v2",
    "set_on": "2026-10-06",
    "currency": "EUR",
    "vat": "excluded",
    "plans": [
      {
        "code": "free",
        "name": "Free",
        "month_eur": 0,
        "year_eur": 0,
        "summary": "Your site on yourname.hypamoon.app, or on your own domain for free, with forms and data. Phone and payments in test mode.",
        "features": [
          "1 project with as many sites as you like on yourname.hypamoon.app, with the Hypamoon badge",
          "Up to 100,000 site requests a month; above that your sites pause until the next month",
          "Connect your own domain for free",
          "Watch up to 3 domains for hijacking, spam and expiry",
          "10 MB of data and 100 MB of files",
          "Version history and rollback for your sites, 30 days",
          "Phone and payments in test mode",
          "1 app and 1 team member",
          "100 sign-in emails a month for the users of your app",
          "50 cents of AI usage a month included, for the portal assistant and chat"
        ],
        "included_per_month": {
          "request": 50000,
          "app_month": 1,
          "seat_month": 1,
          "ai": 0.5
        },
        "sign_in_emails_per_month": 100
      },
      {
        "code": "online",
        "name": "Online",
        "month_eur": 15,
        "year_eur": 165,
        "summary": "Your site or app without the Hypamoon badge, payments on your site, and phone paid per use.",
        "features": [
          "No Hypamoon badge on your site or app",
          "Unlimited sites",
          "Email from your own domain, with the Mail module",
          "Watch up to 25 domains for hijacking, spam and expiry",
          "250 MB of data and 5 GB of files",
          "Payments on your site",
          "Phone, SMS and chat paid per use",
          "3 apps",
          "500 sign-in emails a month for the users of your apps",
          "2 euros of AI usage a month included, for the portal assistant, chat and agents",
          "Version history and rollback for your sites, 30 days"
        ],
        "included_per_month": {
          "request": 1000000,
          "app_month": 3,
          "seat_month": 1,
          "ai": 2
        },
        "sign_in_emails_per_month": 500
      },
      {
        "code": "reachable",
        "name": "Reachable",
        "month_eur": 49,
        "year_eur": 539,
        "summary": "Everything in Online, plus a phone number with a voice AI, SMS and chat included.",
        "features": [
          "Everything in Online",
          "A phone number with 100 receptionist minutes a month",
          "50 SMS a month",
          "17 euros of AI usage a month included, for chat, agents and the portal assistant",
          "1 GB of data and 10 GB of files",
          "1,000 sign-in emails a month for the users of your apps",
          "Version history and rollback for your sites, 90 days"
        ],
        "included_per_month": {
          "request": 1000000,
          "app_month": 3,
          "seat_month": 1,
          "number_month": 1,
          "ai_minute": 100,
          "segment": 50,
          "ai": 17
        },
        "sign_in_emails_per_month": 1000
      },
      {
        "code": "business",
        "name": "Business",
        "month_eur": 149,
        "year_eur": 1639,
        "summary": "Everything in Reachable for a team, with agents, more room, an SLA and fast support.",
        "features": [
          "Everything in Reachable",
          "3 team members and 10 apps",
          "Watch up to 100 domains for hijacking, spam and expiry",
          "Agents, with 20 euros of AI usage a month included for agents, chat and the portal assistant",
          "5 GB of data and 50 GB of files",
          "2,500 sign-in emails a month for the users of your apps",
          "99.9% uptime SLA and support within 4 business hours",
          "Version history and rollback for your sites, 1 year"
        ],
        "included_per_month": {
          "request": 1000000,
          "app_month": 10,
          "seat_month": 3,
          "number_month": 1,
          "ai_minute": 100,
          "segment": 50,
          "ai": 20
        },
        "sign_in_emails_per_month": 2500
      }
    ],
    "sign_in_emails": {
      "what": "Emails your apps send so their users can sign in: sign-in links and codes, invitations and approvals. No other emails.",
      "included_per_month": {
        "free": 100,
        "online": 500,
        "reachable": 1000,
        "business": 2500
      },
      "above_included_with_mail_add_on": "counted in the Mail add-on",
      "above_included_without_mail_add_on_per_day": 20
    },
    "enterprise": {
      "name": "Enterprise",
      "summary": "Custom terms, limits and support, on request.",
      "on_request": true
    },
    "meters": [
      {
        "code": "ai_minute",
        "eur": 0.3,
        "per": 1,
        "description": "per started minute of a voice AI call"
      },
      {
        "code": "minute",
        "eur": 0.15,
        "per": 1,
        "description": "per started minute of a domestic call, all legs together"
      },
      {
        "code": "minute_international",
        "eur": 0.3,
        "per": 1,
        "description": "per started minute of an international call leg"
      },
      {
        "code": "segment",
        "eur": 0.18,
        "per": 1,
        "description": "per SMS segment sent"
      },
      {
        "code": "number_month",
        "eur": 5,
        "per": 1,
        "description": "per number per month"
      },
      {
        "code": "request",
        "eur": 1,
        "per": 1000000,
        "description": "per million site requests"
      },
      {
        "code": "storage_gb_month",
        "eur": 0.05,
        "per": 1,
        "description": "per GB of files per month, prorated per day"
      },
      {
        "code": "data_gb_month",
        "eur": 1.5,
        "per": 1,
        "description": "per GB of data per month, prorated per day"
      },
      {
        "code": "app_month",
        "eur": 2,
        "per": 1,
        "description": "per app per month above the included apps"
      },
      {
        "code": "seat_month",
        "eur": 10,
        "per": 1,
        "description": "per extra team member per month"
      },
      {
        "code": "ai",
        "eur": 1,
        "per": 1,
        "description": "AI usage, billed per use: a chat reply is usually about one cent"
      },
      {
        "code": "cname_month",
        "eur": 2,
        "per": 1,
        "description": "per domain per month connected with a CNAME record"
      }
    ],
    "ai_usage": {
      "meter": "ai",
      "charged_as": "billed per use in euros, first from the AI credit in your plan, then from your balance",
      "minimum_eur_per_model_call": 0.001,
      "typical_eur": {
        "chat_reply": 0.01,
        "agent_model_call": 0.015,
        "portal_assistant_question": 0.05
      },
      "included_per_month_eur": {
        "free": 0.5,
        "online": 2,
        "reachable": 17,
        "business": 20
      }
    },
    "domains": {
      "nameservers_eur": 0,
      "cname_month_eur": 2,
      "at_cost": {
        "card_percent": 3.25,
        "card_fixed_eur": 0.25,
        "registrar_eur_year": 0.24,
        "vat_rate": 0.21
      },
      "premium": {
        "factor": 1.1,
        "plus_eur": 15,
        "max_eur": 500,
        "verified_accounts_only": true
      }
    },
    "transactions": {
      "percent": 1,
      "fixed_eur": 0.1
    },
    "add_ons": [
      {
        "name": "Ship to the stores",
        "month_eur_per_app": 19,
        "builds_per_month": 20
      },
      {
        "name": "Done for you",
        "once_eur_per_platform": 299
      },
      {
        "name": "Mail",
        "what": "Email from your own domain",
        "month_eur": 5,
        "emails_included_per_month": 2000,
        "above_included": {
          "eur": 1.5,
          "per": 1000
        }
      },
      {
        "name": "Dedicated sending IP",
        "what": "Your own IP address for sending email, on request",
        "month_eur_per_ip": 49,
        "plans": [
          "Business",
          "Enterprise"
        ],
        "on_request": true
      }
    ]
  }
}
