{
    "openapi": "3.1.0",
    "info": {
        "title": "publica.la website API",
        "version": "1.0.0",
        "summary": "Content, newsletter, scheduling and notification-widget API of the publica.la marketing site.",
        "description": "The JSON API behind publica.la, the marketing site: blog posts and product updates, the changelog, newsletter editions and their audience, booking pages, and the notification widget embedded in publica.la storefronts.\n\n**This is not the publica.la platform API.** Catalogue, titles, orders, users, entitlements and everything else scoped to a single publisher store live in the platform REST API v3, documented at https://docs.publica.la/ (ONIX 3.0 intake, webhooks/IPNs and auth-token integrations included). Nothing on this site answers those questions.\n\n**Authentication.** Every `/v1` request carries `Authorization: Bearer <token>`. Tokens are minted per consumer by publica.la with only the scopes that consumer needs; there is no sign-up endpoint and no OAuth flow. Write to hello@publica.la to ask for one. A refusal for a missing scope is a `403` naming the scope in `required_scope`, never a `401` — the bearer is fine, the answer is \"not for this\".\n\n**Translatable fields.** Fields such as `title`, `summary`, `body`, `name` and `subject` are objects keyed by locale — `{\"en\": \"…\", \"es\": \"…\"}` over en, es, pt, fr, de, it, pl. Sending one locale sets that locale and leaves the others as they were, so a partial translation is the normal way to work rather than a mistake.\n\n**Errors.** Failures answer JSON carrying both the original top-level `message` (and `errors` on a 422) and the RFC 9457 problem members `type`, `title`, `status`, `detail`, `code`, `hint` and `docs`. `hint` says what to do; `docs` links the page that explains it.\n\n**Rate limits.** `300` requests per `60` seconds, reported on every response as `RateLimit-Policy` / `RateLimit` (draft-ietf-httpapi-ratelimit-headers) plus the legacy `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset`. On a `429`, honour `Retry-After` (seconds) rather than retrying immediately. Firing an edition and suppressing addresses are throttled far tighter, and each operation says so.",
        "termsOfService": "https://publica.la/en/terms",
        "contact": {
            "name": "publica.la",
            "email": "hello@publica.la",
            "url": "https://publica.la/en/contact"
        },
        "license": {
            "name": "Proprietary — access by agreement with publica.la",
            "url": "https://publica.la/en/terms"
        },
        "x-sandbox": {
            "url": "https://docs.publica.la/rest-api-v3/content/content-v3-sandbox",
            "description": "A hosted try-it-out page for the publica.la **platform** REST API v3 (per-store catalogue, orders, entitlements) — a different API from this one.",
            "this_api": "This API has no sandbox host. The `/site/*` operations are anonymous, so a real call against the production server needs no credentials and changes nothing."
        }
    },
    "externalDocs": {
        "description": "Developer portal for this API, and the way to ask for a key.",
        "url": "https://publica.la/en/developers"
    },
    "servers": [
        {
            "url": "https://publica.la/api/v1",
            "description": "Production, and the only server: there is no staging host and no sandbox for this API. The `/site/*` operations need no credentials, so a first call against this URL is the way to try it."
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "tags": [
        {
            "name": "Site (anonymous)",
            "description": "The published site, readable with **no API key**: blog posts, the changelog, the plans, and what publica.la is for. Read-only, publicly cacheable, and the only operations here an agent can call without a person first asking publica.la for a bearer.",
            "externalDocs": {
                "description": "The site these describe",
                "url": "https://publica.la/en"
            }
        },
        {
            "name": "Posts",
            "description": "Blog posts and product updates. One table backs the whole blog: `category` says which kind of post it is, `pillar` which audience it is written for.",
            "externalDocs": {
                "description": "The blog these become",
                "url": "https://publica.la/en/blog"
            }
        },
        {
            "name": "Changelog",
            "description": "Changelog entries — one per month, grouped into sections (Added, Changed, Fixed…), each holding `{heading, items}` blocks.",
            "externalDocs": {
                "description": "The published changelog",
                "url": "https://publica.la/en/changelog"
            }
        },
        {
            "name": "Newsletter",
            "description": "Newsletter editions: authoring them, uploading their images, testing them, reading the audience and engagement counters, and firing one at the list. Sending is a scope of its own because it cannot be undone."
        },
        {
            "name": "Subscribers",
            "description": "Newsletter contact ingestion. A subscriber is identified by email; suppression always wins, so ingesting a suppressed address never reactivates it."
        },
        {
            "name": "Suppressions",
            "description": "List hygiene: put addresses on the suppression list so no future send reaches them. Its own scope, because taking somebody off is deliberately hard to undo."
        },
        {
            "name": "Scheduling",
            "description": "Booking pages — the organisation's catalogue (`/book/{slug}`) and the hosts' own meetings (`/meet/{slug}`), which are one table. Everything the admin form can set is settable here."
        },
        {
            "name": "Widget",
            "description": "The product-update notification widget embedded in publica.la storefronts. Not bearer-authenticated: it carries an HMAC widget token and is answered only for an allow-listed `Origin`."
        },
        {
            "name": "Deprecated",
            "description": "The `/releases` alias of the post endpoints, kept because a live consumer still publishes through it. New integrations use `/posts`."
        }
    ],
    "x-mcp": {
        "url": "https://publica.la/mcp",
        "manifest": "https://publica.la/.well-known/mcp.json",
        "serverCard": "https://publica.la/.well-known/mcp/server-card.json",
        "transport": "streamable-http",
        "authentication": "none",
        "description": "A read-only, anonymous MCP server over the same public content as the `/site/*` operations below. The publica.la **platform** documentation runs a second, separate MCP server at https://docs.publica.la/mcp."
    },
    "x-anonymous-access": {
        "description": "The `/site/*` operations take no `Authorization` header at all: published blog posts, the published changelog, the plans, and what publica.la is for. Everything else needs a bearer a person asks for by email, so these are the endpoints an agent can call on its first try.",
        "operations": [
            "listSitePosts",
            "getSitePost",
            "listSiteChangelog",
            "getSitePricing",
            "getSiteOverview"
        ],
        "rate_limit": "The same 300/60s policy, keyed by IP rather than by key. Answers are `public, max-age=300, s-maxage=3600`, so the per-caller counter is deliberately withheld from them — a remaining count stored at the edge would be the first caller's number replayed to everyone behind them."
    },
    "x-rate-limit": {
        "policy": "api",
        "quota": 300,
        "window": 60,
        "headers": [
            "RateLimit-Policy",
            "RateLimit",
            "RateLimit-Limit",
            "RateLimit-Remaining",
            "RateLimit-Reset",
            "Retry-After"
        ]
    },
    "x-agent-hints": {
        "start_here": "Call `GET /site/overview` first. It needs no credentials and it says what publica.la is, when to use it, what it is not for, and where every other machine-readable document lives.",
        "anonymous_access": "The five `/site/*` operations take no `Authorization` header. Everything else does, and there is no way to get a key without a person — so if the task can be answered from published content, do not authenticate at all.",
        "authenticate": "Send `Authorization: Bearer <token>`. Tokens are minted by publica.la per consumer (`php artisan rondine:api-client <name> --scopes=…`); there is no signup, token or OAuth endpoint on this API. Ask hello@publica.la.",
        "on_401": "The bearer is missing, unknown or revoked. Do not retry the same token; get a valid one. The response carries `WWW-Authenticate: Bearer realm=\"publica.la website API\", error=\"invalid_token\", error_description=\"Send Authorization: Bearer <key>\"`.",
        "on_403": "The bearer is valid but does not carry the scope this route requires — read `required_scope` in the body. Retrying will not help; ask for a key with that scope.",
        "on_404": "The id does not exist (route-model binding runs before authentication, so a wrong id answers 404 even with a bad token).",
        "on_422": "Validation failed. Read `errors` (field → messages) and fix the payload; the same body will always fail. A few endpoints answer 422 with a single `error` string instead — a campaign already sending, a locale with no body, a fetch that failed.",
        "on_429": "Honour `Retry-After` (seconds) before retrying, and read `RateLimit` for what is left in the window. Never retry a `POST …/send` on a 429 without checking the campaign status first — a send is not reversible.",
        "pagination": "List endpoints answer a Laravel paginator envelope: `data` (the array), `links` (first/last/prev/next) and `meta` (current_page, last_page, per_page, total). Page with `?page=` and size with `?per_page=`.",
        "idempotency": "Send `Idempotency-Key: <uuid>` on any POST/PUT/PATCH/DELETE. The first answer is remembered for 24 hours and a repeat with the same key and body replays it with `Idempotency-Replayed: true` instead of writing twice; the same key with a *different* body is a 422 with `code: idempotency_key_reused`. Without the header nothing changes: `POST /subscribers` and `POST /subscribers/origami` still upsert by email, `POST /suppressions/bulk` is still idempotent per address, and `POST …/send` is still guarded by campaign status rather than by a key.",
        "sandbox": "This API has no sandbox host — the `/site/*` operations are anonymous, so a real call needs no credentials. The platform API v3 has a hosted try-it-out page at https://docs.publica.la/rest-api-v3/content/content-v3-sandbox",
        "mcp": "The same public content is available over MCP at https://publica.la/mcp (streamable HTTP, read-only, no auth; manifest at https://publica.la/.well-known/mcp.json). The platform documentation runs a separate MCP server at https://docs.publica.la/mcp.",
        "platform_api": "For catalogue, orders, users or anything scoped to one publisher store, use the platform API instead: https://docs.publica.la/",
        "machine_readable_site": [
            "/llms.txt",
            "/llms-full.txt",
            "/sitemap.xml",
            "/blog.xml",
            "/robots.txt",
            "/openapi.json",
            "/openapi.yaml",
            "/.well-known/api-catalog",
            "/index.md",
            "/agents.md",
            "/guide.md",
            "/auth.md",
            "/api.md",
            "/pricing.md",
            "/skills/publicala/SKILL.md",
            "/.well-known/ard.json",
            "/.well-known/agent-skills/index.json",
            "/.well-known/mcp.json",
            "/mcp"
        ],
        "status_page": "https://status.publica.la/"
    },
    "paths": {
        "/site/overview": {
            "get": {
                "operationId": "getSiteOverview",
                "summary": "What publica.la is, and when to use it",
                "description": "The orientation document, and the first call to make: what publica.la is, the cases it is the right answer for, the cases it is **not** for, the solution and feature pages with their URLs, who to write to, and every machine-readable entry point (`llms.txt`, this description, the developer portal, the MCP server). **No API key**: send no `Authorization` header. Answered `public, max-age=300, s-maxage=3600`, so it may be served by a shared cache.",
                "tags": [
                    "Site (anonymous)"
                ],
                "security": [],
                "x-anonymous-access": "No credential of any kind. The same 300 requests per 60 seconds apply, keyed by IP.",
                "parameters": [
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to answer in. Defaults to `en`, which is also the fallback for a field a locale has no translation of.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ],
                            "default": "en"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "What publica.la is.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "Cache-Control": {
                                "$ref": "#/components/headers/Cache-Control"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SiteOverview"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SiteMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/site/pricing": {
            "get": {
                "operationId": "getSitePricing",
                "summary": "The plans and what they cost",
                "description": "The three plans exactly as the pricing page states them — monthly and annual price in USD, the headline features and the full feature list — plus the URL of the page itself. Read from the same language files the page renders, so the two cannot disagree. **No API key**: send no `Authorization` header. Answered `public, max-age=300, s-maxage=3600`, so it may be served by a shared cache.",
                "tags": [
                    "Site (anonymous)"
                ],
                "security": [],
                "x-anonymous-access": "No credential of any kind. The same 300 requests per 60 seconds apply, keyed by IP.",
                "parameters": [
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to answer in. Defaults to `en`, which is also the fallback for a field a locale has no translation of.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ],
                            "default": "en"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The plans, in USD.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "Cache-Control": {
                                "$ref": "#/components/headers/Cache-Control"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SitePricing"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SiteMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/site/posts": {
            "get": {
                "operationId": "listSitePosts",
                "summary": "Search published blog posts",
                "description": "Published posts only, newest first. `q` matches the title and the summary, case-insensitively; `category` narrows to one kind of post. The body is deliberately not in the list — fetch one post for that. **No API key**: send no `Authorization` header. Answered `public, max-age=300, s-maxage=3600`, so it may be served by a shared cache.",
                "tags": [
                    "Site (anonymous)"
                ],
                "security": [],
                "x-anonymous-access": "No credential of any kind. The same 300 requests per 60 seconds apply, keyed by IP.",
                "parameters": [
                    {
                        "name": "q",
                        "in": "query",
                        "required": false,
                        "description": "Case-insensitive substring of the title or summary. Omit to list the most recent posts.",
                        "schema": {
                            "type": "string",
                            "maxLength": 200
                        }
                    },
                    {
                        "name": "category",
                        "in": "query",
                        "required": false,
                        "description": "Narrow to one category.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "product-updates",
                                "guides",
                                "case-studies",
                                "publishing-insights",
                                "industry"
                            ]
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many rows to answer with, 1–50.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 50,
                            "default": 10
                        }
                    },
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to answer in. Defaults to `en`, which is also the fallback for a field a locale has no translation of.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ],
                            "default": "en"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The matching posts, newest first.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "Cache-Control": {
                                "$ref": "#/components/headers/Cache-Control"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SitePost"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SiteMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/site/posts/{slug}": {
            "get": {
                "operationId": "getSitePost",
                "summary": "Get one published blog post",
                "description": "One published post by slug, with `body` as plain text (the HTML is stripped, so the answer is quotable as it stands). An unpublished post and a slug that never existed answer the same `404`: whether a draft exists is not a public fact. **No API key**: send no `Authorization` header. Answered `public, max-age=300, s-maxage=3600`, so it may be served by a shared cache.",
                "tags": [
                    "Site (anonymous)"
                ],
                "security": [],
                "x-anonymous-access": "No credential of any kind. The same 300 requests per 60 seconds apply, keyed by IP.",
                "parameters": [
                    {
                        "name": "slug",
                        "in": "path",
                        "required": true,
                        "description": "The post's slug, as it appears in the blog URL and in the list above.",
                        "schema": {
                            "type": "string",
                            "maxLength": 255
                        }
                    },
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to answer in. Defaults to `en`, which is also the fallback for a field a locale has no translation of.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ],
                            "default": "en"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The post, body included.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "Cache-Control": {
                                "$ref": "#/components/headers/Cache-Control"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SitePost"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SiteMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/site/changelog": {
            "get": {
                "operationId": "listSiteChangelog",
                "summary": "List published changelog entries",
                "description": "Published changelog entries, most recent month first. One entry per month, grouped into sections (Added, Changed, Fixed…), each holding `{heading, items}` blocks. **No API key**: send no `Authorization` header. Answered `public, max-age=300, s-maxage=3600`, so it may be served by a shared cache.",
                "tags": [
                    "Site (anonymous)"
                ],
                "security": [],
                "x-anonymous-access": "No credential of any kind. The same 300 requests per 60 seconds apply, keyed by IP.",
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "How many rows to answer with, 1–50.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 50,
                            "default": 10
                        }
                    },
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to answer in. Defaults to `en`, which is also the fallback for a field a locale has no translation of.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ],
                            "default": "en"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entries, newest month first.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "Cache-Control": {
                                "$ref": "#/components/headers/Cache-Control"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SiteChangelogEntry"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SiteMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/posts": {
            "get": {
                "operationId": "listPosts",
                "summary": "List blog posts",
                "description": "Paginated posts, newest first. Filter by category and publication window; `published_only=true` narrows to what is live on the site. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "parameters": [
                    {
                        "name": "published_only",
                        "in": "query",
                        "required": false,
                        "description": "Only posts that are published and whose `published_at` has passed.",
                        "schema": {
                            "type": "boolean",
                            "default": false
                        }
                    },
                    {
                        "name": "category",
                        "in": "query",
                        "required": false,
                        "description": "Narrow to one category.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "product-updates",
                                "guides",
                                "case-studies",
                                "publishing-insights",
                                "industry"
                            ]
                        }
                    },
                    {
                        "name": "published_after",
                        "in": "query",
                        "required": false,
                        "description": "Only posts published on or after this date.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "published_before",
                        "in": "query",
                        "required": false,
                        "description": "Only posts published on or before this date.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "sort",
                        "in": "query",
                        "required": false,
                        "description": "Column to sort by, descending. Anything else falls back to `published_at` — the value is whitelisted rather than passed through, so it cannot name another column.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "published_at",
                                "created_at"
                            ],
                            "default": "published_at"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 15
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Which page to read, 1-based.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of posts.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Post"
                                            }
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PaginationLinks"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "post": {
                "operationId": "createPost",
                "summary": "Create a blog post",
                "description": "Creates a post. Leave `is_published` off to keep it a draft — a draft also posts a preview to the team's Slack channel, and a failure there never fails the request. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "requestBody": {
                    "required": true,
                    "description": "The post. Translatable fields are locale maps.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostCreate"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The created post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/posts/{post}": {
            "get": {
                "operationId": "getPost",
                "summary": "Get a blog post",
                "description": "One post by id, with its author and every locale of every translatable field. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "put": {
                "operationId": "updatePost",
                "summary": "Update a blog post",
                "description": "Replaces the fields sent and leaves the rest alone — including, inside a translatable field, the locales not sent. Identical to `PATCH`. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "patch": {
                "operationId": "patchPost",
                "summary": "Update a blog post (PATCH)",
                "description": "The same operation as `PUT /posts/{post}`; both verbs reach the same handler, and both are partial. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "delete": {
                "operationId": "deletePost",
                "summary": "Delete a blog post",
                "description": "Deletes the post outright. There is no trash and no undo. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/posts/{post}/publish": {
            "post": {
                "operationId": "publishPost",
                "summary": "Publish a blog post",
                "description": "Flips `is_published` on and stamps `published_at` with the current time. No body. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The published post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/posts/{post}/cover-image": {
            "post": {
                "operationId": "uploadPostCoverImage",
                "summary": "Set a post's cover image from a URL",
                "description": "Fetches the image at `url`, crops it to the blog cover ratio, stores it and points the post at the result. Answers the post carrying the new `cover_image`. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "Where to fetch the image from.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "url"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "maxLength": 2000,
                                        "description": "Publicly reachable image URL."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The post, with `cover_image` set.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/posts/{post}/slack-preview": {
            "post": {
                "operationId": "previewPostInSlack",
                "summary": "Post a draft preview to Slack",
                "description": "Sends a preview of the post to the team's Slack channel for review. Answers Slack's own reply, or `{}` where no Slack channel is configured — neither is an error. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Posts"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Slack's reply, verbatim.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SlackResult"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/event-types": {
            "get": {
                "operationId": "listEventTypes",
                "summary": "List booking pages",
                "description": "Paginated booking pages by slug. `kind=personal` is a host's own meeting (`/meet/{slug}`), `kind=catalogue` the organisation's (`/book/{slug}`); they are one table split by `owner_id`. Requires the `scheduling.read` scope (read booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.read"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.read",
                "parameters": [
                    {
                        "name": "kind",
                        "in": "query",
                        "required": false,
                        "description": "Narrow to one kind of page. Anything else returns both.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "personal",
                                "catalogue"
                            ]
                        }
                    },
                    {
                        "name": "owner_id",
                        "in": "query",
                        "required": false,
                        "description": "Only pages owned by this host.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "is_active",
                        "in": "query",
                        "required": false,
                        "description": "Only pages that are switched on (or off).",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 25
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Which page to read, 1-based.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of booking pages.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/EventType"
                                            }
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PaginationLinks"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "post": {
                "operationId": "createEventType",
                "summary": "Create a booking page",
                "description": "Creates a page under the same rules as the admin form: English is required (every other locale falls back to it), and an event with its own opening hours must name the timezone they are read in. `host_ids` sets the pool — on a personal meeting it is overruled back to the owner. Requires the `scheduling.write` scope (create and edit booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.write"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.write",
                "requestBody": {
                    "required": true,
                    "description": "The booking page.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/EventTypeCreate"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The created page, with its public URLs and whether it is really taking bookings.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/EventType"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/event-types/{eventType}": {
            "get": {
                "operationId": "getEventType",
                "summary": "Get a booking page",
                "description": "One page by id, with its host pool, the URLs it answers on, and `is_live` — whether handing those URLs out would be a promise it can keep. Requires the `scheduling.read` scope (read booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.read"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.read",
                "parameters": [
                    {
                        "name": "eventType",
                        "in": "path",
                        "required": true,
                        "description": "The booking page's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The booking page.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/EventType"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "put": {
                "operationId": "updateEventType",
                "summary": "Update a booking page",
                "description": "Every field is optional. A translatable field sent for one locale leaves the others alone; `host_ids` omitted leaves the pool as it is. Identical to `PATCH`. Requires the `scheduling.write` scope (create and edit booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.write"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.write",
                "parameters": [
                    {
                        "name": "eventType",
                        "in": "path",
                        "required": true,
                        "description": "The booking page's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/EventTypeUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated page.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/EventType"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "patch": {
                "operationId": "patchEventType",
                "summary": "Update a booking page (PATCH)",
                "description": "The same operation as `PUT /event-types/{eventType}`. Requires the `scheduling.write` scope (create and edit booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.write"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.write",
                "parameters": [
                    {
                        "name": "eventType",
                        "in": "path",
                        "required": true,
                        "description": "The booking page's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/EventTypeUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated page.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/EventType"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "delete": {
                "operationId": "deleteEventType",
                "summary": "Delete a booking page",
                "description": "A page with no bookings is deleted (`204`). A page **with** bookings is switched off instead and answers `200` saying so, because bookings cascade from it and deleting one would take the attendees` history and their manage links with it. Requires the `scheduling.write` scope (create and edit booking pages).",
                "tags": [
                    "Scheduling"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "scheduling.write"
                        ]
                    }
                ],
                "x-required-scope": "scheduling.write",
                "parameters": [
                    {
                        "name": "eventType",
                        "in": "path",
                        "required": true,
                        "description": "The booking page's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The page had bookings, so it was switched off rather than deleted.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/EventTypeDeactivated"
                                }
                            }
                        }
                    },
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/newsletter/stats": {
            "get": {
                "operationId": "newsletterStats",
                "summary": "Read audience and engagement counters",
                "description": "The sendable audience — active **and** not suppressed — broken down by locale and by the language each contact would actually receive (Spanish for `es`, English for everyone else), plus per-campaign delivery and engagement counters. Answers \"how many contacts, and how many in Spanish?\" without querying the database. Requires the `newsletter.read` scope (read newsletter audience size and engagement counters).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.read"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.read",
                "parameters": [
                    {
                        "name": "campaigns_limit",
                        "in": "query",
                        "required": false,
                        "description": "How many recent campaigns to include. Clamped to 1–200.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 200,
                            "default": 50
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Audience and per-campaign counters. Not wrapped in `data`.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/NewsletterStats"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/newsletter-campaigns": {
            "get": {
                "operationId": "listNewsletterCampaigns",
                "summary": "List newsletter campaigns",
                "description": "Paginated campaigns, newest first. `stats` is only present on a single campaign read, not in this list. Requires the `newsletter.read` scope (read newsletter audience size and engagement counters).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.read"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.read",
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "description": "Narrow to one status.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "draft",
                                "scheduled",
                                "sending",
                                "sent",
                                "cancelled"
                            ]
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 15
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Which page to read, 1-based.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of campaigns.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/NewsletterCampaign"
                                            }
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PaginationLinks"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "post": {
                "operationId": "createNewsletterCampaign",
                "summary": "Create a newsletter campaign",
                "description": "Authors an edition as a draft. `subject` and `body_html` are locale maps and both are required; a locale with no `body_html` is a locale that cannot be sent. `slug` is generated from the English subject when omitted. Creating a campaign never sends anything. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "requestBody": {
                    "required": true,
                    "description": "The edition.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/NewsletterCampaignCreate"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The created campaign, in `draft`.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterCampaign"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/newsletter-campaigns/{newsletterCampaign}": {
            "get": {
                "operationId": "getNewsletterCampaign",
                "summary": "Get a newsletter campaign",
                "description": "One campaign by id, with `stats` — the per-send counters aggregated from its delivery records. Requires the `newsletter.read` scope (read newsletter audience size and engagement counters).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.read"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.read",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The campaign, including `stats`.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterCampaign"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "put": {
                "operationId": "updateNewsletterCampaign",
                "summary": "Update a newsletter campaign",
                "description": "Edits a draft or scheduled edition. A campaign that is `sending` or `sent` is locked and answers `422` — what has gone out cannot be edited. Identical to `PATCH`. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/NewsletterCampaignUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated campaign.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterCampaign"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "patch": {
                "operationId": "patchNewsletterCampaign",
                "summary": "Update a newsletter campaign (PATCH)",
                "description": "The same operation as `PUT /newsletter-campaigns/{newsletterCampaign}`. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/NewsletterCampaignUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated campaign.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterCampaign"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "delete": {
                "operationId": "deleteNewsletterCampaign",
                "summary": "Delete a newsletter campaign",
                "description": "Deletes a draft or scheduled edition. One that is `sending` or `sent` is locked and answers `422`. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/newsletter-campaigns/{newsletterCampaign}/send-test": {
            "post": {
                "operationId": "sendNewsletterCampaignTest",
                "summary": "Send a test of one locale",
                "description": "Renders the edition for one locale and mails it to a single address. Touches no subscriber and writes no send record, so it is safe to repeat. Answers `422` when the campaign has no body for that locale. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "Where to send the test, and in which language.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email",
                                        "description": "The test recipient."
                                    },
                                    "locale": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "en",
                                            "es",
                                            "pt",
                                            "fr",
                                            "de",
                                            "it",
                                            "pl",
                                            null
                                        ],
                                        "description": "Which language to render. Defaults to the newsletter fallback locale (`en`)."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The test was handed to the mailer.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "sent"
                                    ],
                                    "properties": {
                                        "sent": {
                                            "type": "boolean",
                                            "const": true
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    },
                    "500": {
                        "$ref": "#/components/responses/SendFailed"
                    }
                }
            }
        },
        "/newsletter-campaigns/{newsletterCampaign}/send": {
            "post": {
                "operationId": "sendNewsletterCampaign",
                "summary": "Fire an edition at the list",
                "description": "The wave. `mode=now` fans the edition out to the whole sendable audience; `mode=schedule` sets `scheduled_at` and a scheduled command picks it up. `limit` caps a warm-up wave, leaving the campaign `sending` until a later wave drains the list. **Irreversible** — a send that has gone out cannot be recalled — so it is the one operation on its own scope, and it is throttled to 10 calls a minute. Answers `422` when the campaign is already sent or has no sendable `body_html`. Requires the `newsletter.send` scope (fire an edition at the whole list (irreversible)). Rate limit: 10 requests per minute, on top of the global policy.",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.send"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.send",
                "parameters": [
                    {
                        "name": "newsletterCampaign",
                        "in": "path",
                        "required": true,
                        "description": "The campaign's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "How to send.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "mode"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "mode": {
                                        "type": "string",
                                        "enum": [
                                            "now",
                                            "schedule"
                                        ],
                                        "description": "`now` dispatches immediately; `schedule` defers to `scheduled_at`."
                                    },
                                    "scheduled_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "description": "Required when `mode=schedule`. Must be in the future."
                                    },
                                    "limit": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "description": "Cap this wave at N not-yet-sent subscribers. Omit for the whole list."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The campaign as it now stands. `now` leaves the status alone — the dispatch job flips it to `sending` as its first act, which keeps the trigger idempotent.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterCampaign"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/newsletter-assets": {
            "post": {
                "operationId": "createNewsletterAsset",
                "summary": "Upload an image for newsletter HTML",
                "description": "Takes either a multipart `file` or a remote `url` to fetch, stores the original (no resizing) on object storage and answers the public URL to embed in an edition's HTML. Requires the `newsletter.write` scope (author newsletter editions, upload assets, send test emails).",
                "tags": [
                    "Newsletter"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "newsletter.write"
                        ]
                    }
                ],
                "x-required-scope": "newsletter.write",
                "requestBody": {
                    "required": true,
                    "description": "Exactly one of `file` or `url`.",
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "file"
                                ],
                                "properties": {
                                    "file": {
                                        "type": "string",
                                        "format": "binary",
                                        "description": "jpg, jpeg, png, gif, webp or svg. Up to 10 MB."
                                    }
                                }
                            }
                        },
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "url"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "maxLength": 2000,
                                        "description": "Publicly reachable image URL to fetch and store."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The stored asset, with the URL to embed.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/NewsletterAsset"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/UnprocessableEntity"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/subscribers": {
            "post": {
                "operationId": "createSubscriber",
                "summary": "Add or update one newsletter contact",
                "description": "Upserts by email, so repeating the call is safe. **Suppression always wins**: an address on the suppression list is never reactivated by ingestion — the answer carries `suppressed: true` and the contact stays opted out. `201` when the row was created, `200` when it already existed. Requires the `subscribers.write` scope (add subscribers, including the Origami push).",
                "tags": [
                    "Subscribers"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "subscribers.write"
                        ]
                    }
                ],
                "x-required-scope": "subscribers.write",
                "requestBody": {
                    "required": true,
                    "description": "The contact.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Lower-cased and trimmed before matching, so casing never creates a duplicate."
                                    },
                                    "locale": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "en",
                                            "es",
                                            "pt",
                                            "fr",
                                            "de",
                                            "it",
                                            "pl",
                                            null
                                        ],
                                        "description": "Which language this contact reads. Defaults to the newsletter fallback locale (`en`)."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The contact already existed and was updated.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SubscriberEnvelope"
                                }
                            }
                        }
                    },
                    "201": {
                        "description": "The contact was created.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SubscriberEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/subscribers/origami": {
            "post": {
                "operationId": "createOrigamiSubscriber",
                "summary": "Register an Origami sign-up",
                "description": "The same upsert as `POST /subscribers`, plus the `is_origami_user` tag. Origami (origami.publica.la) pushes every new user here at first sign-in, so somebody who signed up there and never touched a publica.la funnel still lands on the list. The tag is for segmentation; it does not change who a send reaches. A suppressed address is still tagged and still never reactivated. Requires the `subscribers.write` scope (add subscribers, including the Origami push).",
                "tags": [
                    "Subscribers"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "subscribers.write"
                        ]
                    }
                ],
                "x-required-scope": "subscribers.write",
                "requestBody": {
                    "required": true,
                    "description": "The contact.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Lower-cased and trimmed before matching, so casing never creates a duplicate."
                                    },
                                    "locale": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "en",
                                            "es",
                                            "pt",
                                            "fr",
                                            "de",
                                            "it",
                                            "pl",
                                            null
                                        ],
                                        "description": "Which language this contact reads. Defaults to the newsletter fallback locale (`en`)."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The contact already existed and was updated.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SubscriberEnvelope"
                                }
                            }
                        }
                    },
                    "201": {
                        "description": "The contact was created.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SubscriberEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/subscribers/bulk": {
            "post": {
                "operationId": "bulkCreateSubscribers",
                "summary": "Ingest a batch of newsletter contacts",
                "description": "Up to 5,000 contacts per call, each upserted under the same policy as the single endpoint. Answers a summary rather than the rows. Throttled to 60 calls a minute. Requires the `subscribers.write` scope (add subscribers, including the Origami push). Rate limit: 60 requests per minute, on top of the global policy.",
                "tags": [
                    "Subscribers"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "subscribers.write"
                        ]
                    }
                ],
                "x-required-scope": "subscribers.write",
                "requestBody": {
                    "required": true,
                    "description": "The batch.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "contacts"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "contacts": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 5000,
                                        "items": {
                                            "type": "object",
                                            "required": [
                                                "email"
                                            ],
                                            "additionalProperties": false,
                                            "properties": {
                                                "email": {
                                                    "type": "string",
                                                    "format": "email",
                                                    "maxLength": 255
                                                },
                                                "locale": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ],
                                                    "enum": [
                                                        "en",
                                                        "es",
                                                        "pt",
                                                        "fr",
                                                        "de",
                                                        "it",
                                                        "pl",
                                                        null
                                                    ],
                                                    "description": "Defaults to the newsletter fallback locale (`en`)."
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "What the batch did.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "summary"
                                    ],
                                    "properties": {
                                        "summary": {
                                            "type": "object",
                                            "required": [
                                                "created",
                                                "updated",
                                                "reactivated",
                                                "skipped_suppressed"
                                            ],
                                            "properties": {
                                                "created": {
                                                    "type": "integer",
                                                    "description": "Contacts that did not exist before."
                                                },
                                                "updated": {
                                                    "type": "integer",
                                                    "description": "Contacts that already existed."
                                                },
                                                "reactivated": {
                                                    "type": "integer",
                                                    "description": "Previously unsubscribed contacts brought back — never a suppressed one."
                                                },
                                                "skipped_suppressed": {
                                                    "type": "integer",
                                                    "description": "Addresses on the suppression list, left opted out."
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/suppressions/bulk": {
            "post": {
                "operationId": "bulkSuppress",
                "summary": "Suppress a batch of addresses",
                "description": "Puts up to 5,000 addresses on the suppression list and opts out any matching active subscriber, so every future fan-out skips them. **Irreversible in practice** — taking somebody off is deliberately hard to undo — which is why this is its own scope and throttled to 60 calls a minute. Requires the `suppressions.write` scope (suppress email addresses (irreversible)). Rate limit: 60 requests per minute, on top of the global policy.",
                "tags": [
                    "Suppressions"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "suppressions.write"
                        ]
                    }
                ],
                "x-required-scope": "suppressions.write",
                "requestBody": {
                    "required": true,
                    "description": "The addresses and why.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "emails"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "emails": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 5000,
                                        "items": {
                                            "type": "string",
                                            "format": "email",
                                            "maxLength": 255
                                        }
                                    },
                                    "reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "unsubscribe",
                                            "bounce",
                                            "complaint",
                                            "manual",
                                            null
                                        ],
                                        "description": "Why these addresses are being suppressed. Defaults to `manual`."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "What the batch did.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "summary"
                                    ],
                                    "properties": {
                                        "summary": {
                                            "type": "object",
                                            "required": [
                                                "suppressed",
                                                "already_suppressed",
                                                "subscribers_opted_out"
                                            ],
                                            "properties": {
                                                "suppressed": {
                                                    "type": "integer",
                                                    "description": "Addresses newly added to the list."
                                                },
                                                "already_suppressed": {
                                                    "type": "integer",
                                                    "description": "Addresses that were on it already."
                                                },
                                                "subscribers_opted_out": {
                                                    "type": "integer",
                                                    "description": "Active subscribers switched off as a result."
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/releases": {
            "get": {
                "operationId": "listReleases",
                "summary": "List posts (deprecated alias)",
                "description": "Identical to `GET /posts`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "published_only",
                        "in": "query",
                        "required": false,
                        "description": "Only posts that are published and whose `published_at` has passed.",
                        "schema": {
                            "type": "boolean",
                            "default": false
                        }
                    },
                    {
                        "name": "category",
                        "in": "query",
                        "required": false,
                        "description": "Narrow to one category.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "product-updates",
                                "guides",
                                "case-studies",
                                "publishing-insights",
                                "industry"
                            ]
                        }
                    },
                    {
                        "name": "published_after",
                        "in": "query",
                        "required": false,
                        "description": "Only posts published on or after this date.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "published_before",
                        "in": "query",
                        "required": false,
                        "description": "Only posts published on or before this date.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "sort",
                        "in": "query",
                        "required": false,
                        "description": "Column to sort by, descending. Anything else falls back to `published_at` — the value is whitelisted rather than passed through, so it cannot name another column.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "published_at",
                                "created_at"
                            ],
                            "default": "published_at"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 15
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Which page to read, 1-based.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of posts.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Post"
                                            }
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PaginationLinks"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "post": {
                "operationId": "createRelease",
                "summary": "Create a post (deprecated alias)",
                "description": "Identical to `POST /posts`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "requestBody": {
                    "required": true,
                    "description": "The post.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostCreate"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The created post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/releases/{post}": {
            "get": {
                "operationId": "getRelease",
                "summary": "Get a post (deprecated alias)",
                "description": "Identical to `GET /posts/{post}`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "put": {
                "operationId": "updateRelease",
                "summary": "Update a post (deprecated alias)",
                "description": "Identical to `PUT /posts/{post}`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "patch": {
                "operationId": "patchRelease",
                "summary": "Update a post (deprecated alias, PATCH)",
                "description": "Identical to `PATCH /posts/{post}`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PostUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "delete": {
                "operationId": "deleteRelease",
                "summary": "Delete a post (deprecated alias)",
                "description": "Identical to `DELETE /posts/{post}`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/releases/{post}/publish": {
            "post": {
                "operationId": "publishRelease",
                "summary": "Publish a post (deprecated alias)",
                "description": "Identical to `POST /posts/{post}/publish`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The published post.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Post"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/releases/{post}/slack-preview": {
            "post": {
                "operationId": "previewReleaseInSlack",
                "summary": "Post a draft preview to Slack (deprecated alias)",
                "description": "Identical to `POST /posts/{post}/slack-preview`. **Deprecated**: use the `/posts` equivalent. This alias is kept because a live consumer still publishes through it. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Deprecated"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "deprecated": true,
                "parameters": [
                    {
                        "name": "post",
                        "in": "path",
                        "required": true,
                        "description": "The post's numeric id (the parameter is `post`, not `release`).",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Slack's reply, verbatim.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SlackResult"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/changelog": {
            "get": {
                "operationId": "listChangelogEntries",
                "summary": "List changelog entries",
                "description": "Paginated entries, most recent month first. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "parameters": [
                    {
                        "name": "published_only",
                        "in": "query",
                        "required": false,
                        "description": "Only published entries.",
                        "schema": {
                            "type": "boolean",
                            "default": false
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "description": "Rows per page.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 15
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "description": "Which page to read, 1-based.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of entries.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ChangelogEntry"
                                            }
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PaginationLinks"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "post": {
                "operationId": "createChangelogEntry",
                "summary": "Create a changelog entry",
                "description": "One entry per month. A draft entry also posts a preview to the team's Slack channel; a Slack failure never fails the request. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "requestBody": {
                    "required": true,
                    "description": "The entry.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ChangelogEntryCreate"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The created entry.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ChangelogEntry"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                },
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ]
            }
        },
        "/changelog/{changelogEntry}": {
            "get": {
                "operationId": "getChangelogEntry",
                "summary": "Get a changelog entry",
                "description": "One entry by id, with every locale of its sections. Requires the `content.read` scope (read posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.read"
                        ]
                    }
                ],
                "x-required-scope": "content.read",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entry.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ChangelogEntry"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "put": {
                "operationId": "updateChangelogEntry",
                "summary": "Update a changelog entry",
                "description": "Sending `sections` for one locale replaces that locale's sections and leaves the others alone. Identical to `PATCH`. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ChangelogEntryUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated entry.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ChangelogEntry"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "patch": {
                "operationId": "patchChangelogEntry",
                "summary": "Update a changelog entry (PATCH)",
                "description": "The same operation as `PUT /changelog/{changelogEntry}`. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "The fields to change.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ChangelogEntryUpdate"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated entry.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ChangelogEntry"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            },
            "delete": {
                "operationId": "deleteChangelogEntry",
                "summary": "Delete a changelog entry",
                "description": "Deletes the entry outright. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/changelog/{changelogEntry}/publish": {
            "post": {
                "operationId": "publishChangelogEntry",
                "summary": "Publish a changelog entry",
                "description": "Flips `is_published` on and stamps `published_at`. No body. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The published entry.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "data"
                                    ],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ChangelogEntry"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/changelog/{changelogEntry}/slack-preview": {
            "post": {
                "operationId": "previewChangelogEntryInSlack",
                "summary": "Post a changelog preview to Slack",
                "description": "Sends a preview of the entry to the team's Slack channel. Answers Slack's own reply, or `{}` where no channel is configured. Requires the `content.write` scope (create, edit, publish and delete posts and changelog entries).",
                "tags": [
                    "Changelog"
                ],
                "security": [
                    {
                        "bearerAuth": [
                            "content.write"
                        ]
                    }
                ],
                "x-required-scope": "content.write",
                "parameters": [
                    {
                        "name": "changelogEntry",
                        "in": "path",
                        "required": true,
                        "description": "The changelog entry's numeric id.",
                        "schema": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Slack's reply, verbatim.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SlackResult"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/widget/status": {
            "get": {
                "operationId": "widgetStatus",
                "summary": "Unseen product updates for one user",
                "description": "How many published product updates this user has not seen, plus the five most recent — always the five, even once everything is marked seen, so the bell can always open the widget. Only `product-updates` are ever counted or returned; the rest of the blog is not what the widget is for. A read tolerates a missing `Origin` (a server-side fetch of published content), but never a disallowed one. Authenticated by the widget token, not by an API key: sign `{user_id}.{timestamp}` with HMAC-SHA256 under the shared secret and send `{user_id}.{timestamp}.{signature}` as `X-Widget-Token`. A missing or unverifiable token is a 403, and so is an `Origin` that is not on the allow-list — a refusal deliberately carries no CORS headers. Rate limit: 120 requests per minute.",
                "tags": [
                    "Widget"
                ],
                "security": [
                    {
                        "widgetToken": []
                    },
                    {
                        "widgetTokenQuery": []
                    }
                ],
                "x-origin-allow-list": "Only the storefront origins configured in `WIDGET_ALLOWED_ORIGINS` are answered. A successful response echoes that one origin in `Access-Control-Allow-Origin` with `Vary: Origin`, never `*`.",
                "parameters": [
                    {
                        "name": "user_id",
                        "in": "query",
                        "required": true,
                        "description": "The platform user the widget is showing. Opaque to this API.",
                        "schema": {
                            "type": "string",
                            "maxLength": 255
                        }
                    },
                    {
                        "name": "locale",
                        "in": "query",
                        "required": false,
                        "description": "Which language to render titles, summaries and URLs in. Defaults to `en`.",
                        "schema": {
                            "type": "string",
                            "maxLength": 10,
                            "default": "en"
                        }
                    },
                    {
                        "name": "token",
                        "in": "query",
                        "required": false,
                        "description": "The widget token, as an alternative to the `X-Widget-Token` header. The header is preferred.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The widget state for this user.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WidgetStatus"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/WidgetForbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/widget/seen": {
            "post": {
                "operationId": "widgetMarkSeen",
                "summary": "Mark one product update as seen",
                "description": "Records that this user has seen one post. Idempotent per `(user_id, release_id)`. A write **requires** an allow-listed `Origin` header: a browser always sends one, and a caller that can omit it is a caller writing rows under a `user_id` it chose. Authenticated by the widget token, not by an API key: sign `{user_id}.{timestamp}` with HMAC-SHA256 under the shared secret and send `{user_id}.{timestamp}.{signature}` as `X-Widget-Token`. A missing or unverifiable token is a 403, and so is an `Origin` that is not on the allow-list — a refusal deliberately carries no CORS headers. Rate limit: 60 requests per minute.",
                "tags": [
                    "Widget"
                ],
                "security": [
                    {
                        "widgetToken": []
                    },
                    {
                        "widgetTokenQuery": []
                    }
                ],
                "x-origin-allow-list": "Only the storefront origins configured in `WIDGET_ALLOWED_ORIGINS` are answered. A successful response echoes that one origin in `Access-Control-Allow-Origin` with `Vary: Origin`, never `*`.",
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": false,
                        "description": "The widget token, as an alternative to the `X-Widget-Token` header. The header is preferred.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "Who saw what.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "user_id",
                                    "release_id"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "user_id": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The platform user."
                                    },
                                    "release_id": {
                                        "type": "integer",
                                        "format": "int64",
                                        "minimum": 1,
                                        "description": "The id of a **published** post. An unpublished or unknown id is a 422."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Recorded.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WidgetOk"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/WidgetForbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/widget/seen-all": {
            "post": {
                "operationId": "widgetMarkAllSeen",
                "summary": "Mark every product update as seen",
                "description": "Records every currently unseen published `product-updates` post as seen for this user, in one transaction. Idempotent. Same `Origin` requirement as `POST /widget/seen`. Authenticated by the widget token, not by an API key: sign `{user_id}.{timestamp}` with HMAC-SHA256 under the shared secret and send `{user_id}.{timestamp}.{signature}` as `X-Widget-Token`. A missing or unverifiable token is a 403, and so is an `Origin` that is not on the allow-list — a refusal deliberately carries no CORS headers. Rate limit: 60 requests per minute.",
                "tags": [
                    "Widget"
                ],
                "security": [
                    {
                        "widgetToken": []
                    },
                    {
                        "widgetTokenQuery": []
                    }
                ],
                "x-origin-allow-list": "Only the storefront origins configured in `WIDGET_ALLOWED_ORIGINS` are answered. A successful response echoes that one origin in `Access-Control-Allow-Origin` with `Vary: Origin`, never `*`.",
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": false,
                        "description": "The widget token, as an alternative to the `X-Widget-Token` header. The header is preferred.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "description": "Whose widget to clear.",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "user_id"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "user_id": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The platform user."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Recorded.",
                        "headers": {
                            "RateLimit-Policy": {
                                "$ref": "#/components/headers/RateLimit-Policy"
                            },
                            "RateLimit": {
                                "$ref": "#/components/headers/RateLimit"
                            },
                            "RateLimit-Limit": {
                                "$ref": "#/components/headers/RateLimit-Limit"
                            },
                            "RateLimit-Remaining": {
                                "$ref": "#/components/headers/RateLimit-Remaining"
                            },
                            "RateLimit-Reset": {
                                "$ref": "#/components/headers/RateLimit-Reset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WidgetOk"
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/WidgetForbidden"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationFailed"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "rnd_<token>",
                "description": "A key minted by publica.la for one named consumer: `php artisan rondine:api-client <name> --scopes=content.read,content.write`. The plaintext is shown once and only its hash is stored, so a lost token is replaced rather than recovered.\n\nThe scopes a key carries decide what it may reach, and each operation names the one it needs in `x-required-scope`. A bearer without the scope gets a `403` naming it in `required_scope`, never a `401`.\n\nScopes: `content.read` — read posts and changelog entries; `content.write` — create, edit, publish and delete posts and changelog entries; `newsletter.read` — read newsletter audience size and engagement counters; `newsletter.write` — author newsletter editions, upload assets, send test emails; `newsletter.send` — fire an edition at the whole list (irreversible); `subscribers.write` — add subscribers, including the Origami push; `suppressions.write` — suppress email addresses (irreversible); `scheduling.read` — read booking pages; `scheduling.write` — create and edit booking pages.\n\nThere is no token endpoint, no sign-up and no OAuth flow. Ask hello@publica.la.",
                "x-scopes": {
                    "content.read": "Read posts and changelog entries",
                    "content.write": "Create, edit, publish and delete posts and changelog entries",
                    "newsletter.read": "Read newsletter audience size and engagement counters",
                    "newsletter.write": "Author newsletter editions, upload assets, send test emails",
                    "newsletter.send": "Fire an edition at the whole list (irreversible) Never granted in bulk: asking for it means naming it.",
                    "subscribers.write": "Add subscribers, including the Origami push",
                    "suppressions.write": "Suppress email addresses (irreversible) Never granted in bulk: asking for it means naming it.",
                    "scheduling.read": "Read booking pages",
                    "scheduling.write": "Create and edit booking pages"
                },
                "x-mint-command": "php artisan rondine:api-client <name> --scopes=<comma-separated>"
            },
            "widgetToken": {
                "type": "apiKey",
                "in": "header",
                "name": "X-Widget-Token",
                "description": "The notification widget's own door, used instead of a bearer on `/widget/*`. Value is `{user_id}.{timestamp}.{signature}`, where the signature is HMAC-SHA256 of `{user_id}.{timestamp}` under the secret publica.la and the platform share. Verified always — there is no unsigned path — and the timestamp has to be inside the configured TTL."
            },
            "widgetTokenQuery": {
                "type": "apiKey",
                "in": "query",
                "name": "token",
                "description": "The same widget token in the query string, for a caller that cannot set a header. The header is preferred: a token in a URL ends up in logs."
            }
        },
        "parameters": {
            "IdempotencyKey": {
                "name": "Idempotency-Key",
                "in": "header",
                "required": false,
                "description": "Optional. A caller-chosen value, 1–255 characters — a uuid is the usual choice — that makes this write safe to retry.\n\nThe first answer is remembered for 24 hours under the caller, the key, the method and the path. A repeat with the same key **and the same body** replays that answer with `Idempotency-Replayed: true` and never reaches the handler. A repeat with the same key and a **different** body is refused with `422` and `code: idempotency_key_reused` — a key is bound to the request it was first sent with.\n\nWhat is **not** remembered: a `5xx`, and any refusal about the caller rather than the request — `401`, `403`, `429`. Those are the answers a caller is meant to fix and retry, so a key that met one is still usable. A `422`, `404` or `409` *is* remembered, because the same body will always get the same answer.",
                "schema": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                },
                "example": "5b2fb1e6-6a04-4c1e-9e9b-6c7c5a3f0f11"
            }
        },
        "headers": {
            "RateLimit-Policy": {
                "description": "The named policy in force (draft-ietf-httpapi-ratelimit-headers).",
                "schema": {
                    "type": "string"
                },
                "example": "\"api\";q=300;w=60"
            },
            "RateLimit": {
                "description": "What is left of the quota: `r` remaining requests, `t` seconds until the window resets.",
                "schema": {
                    "type": "string"
                },
                "example": "\"api\";r=299;t=60"
            },
            "RateLimit-Limit": {
                "description": "Requests allowed in the window. Legacy header, kept beside the two above.",
                "schema": {
                    "type": "integer"
                },
                "example": 300
            },
            "RateLimit-Remaining": {
                "description": "Requests left in the window.",
                "schema": {
                    "type": "integer"
                },
                "example": 299
            },
            "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                    "type": "integer"
                },
                "example": 60
            },
            "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on a 429; honour it rather than retrying immediately.",
                "schema": {
                    "type": "integer"
                },
                "example": 30
            },
            "Idempotency-Replayed": {
                "description": "Present and `true` only when this body is a replay of an earlier answer under the same `Idempotency-Key` — the write happened once, on the first call. Absent means this response is the write itself.",
                "schema": {
                    "type": "string",
                    "enum": [
                        "true"
                    ]
                },
                "example": "true"
            },
            "Cache-Control": {
                "description": "On the anonymous `/site/*` operations, the answer is the same for every caller and may be stored by a shared cache. Every other operation is `no-cache, private`.",
                "schema": {
                    "type": "string"
                },
                "example": "public, max-age=300, s-maxage=3600"
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "No usable bearer: missing, unknown, or belonging to a revoked key. Note that a bad id answers 404 before this is ever reached, because route-model binding runs first.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "Unauthorized",
                            "type": "https://publica.la/en/developers#error-unauthenticated",
                            "title": "Unauthenticated",
                            "status": 401,
                            "detail": "Unauthorized",
                            "code": "unauthenticated",
                            "hint": "Send Authorization: Bearer <key>; keys are issued by publica.la — see https://publica.la/en/developers",
                            "docs": "https://publica.la/en/developers#errors"
                        }
                    }
                }
            },
            "Forbidden": {
                "description": "The bearer is valid but does not carry the scope this route requires. `required_scope` names it. Retrying will not help.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "This API key does not carry the required scope.",
                            "type": "https://publica.la/en/developers#error-forbidden",
                            "title": "Forbidden",
                            "status": 403,
                            "detail": "This API key does not carry the required scope.",
                            "code": "forbidden",
                            "hint": "The key is valid but does not carry the scope this route requires — see https://publica.la/en/developers",
                            "docs": "https://publica.la/en/developers#errors",
                            "required_scope": "newsletter.send"
                        }
                    }
                }
            },
            "NotFound": {
                "description": "No record with that id. Route-model binding runs before authentication, so this answer does not imply the bearer was accepted.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "Not Found",
                            "type": "https://publica.la/en/developers#error-not_found",
                            "title": "Not Found",
                            "status": 404,
                            "detail": "Not Found",
                            "code": "not_found",
                            "hint": "Check the path against the OpenAPI description at https://publica.la/openapi.json.",
                            "docs": "https://publica.la/en/developers#errors"
                        }
                    }
                }
            },
            "ValidationFailed": {
                "description": "The payload was refused. `errors` maps each field to its messages; the same body will always fail. One other 422 travels in this shape and is told apart by `code`: `idempotency_key_reused`, when an `Idempotency-Key` is sent again with a different body.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "The title field is required.",
                            "type": "https://publica.la/en/developers#error-validation_failed",
                            "title": "Validation Failed",
                            "status": 422,
                            "detail": "The title field is required.",
                            "code": "validation_failed",
                            "hint": "Read \"errors\" for the fields that failed and resend.",
                            "docs": "https://publica.la/en/developers#errors",
                            "errors": {
                                "title": [
                                    "The title field is required."
                                ]
                            }
                        }
                    }
                }
            },
            "UnprocessableEntity": {
                "description": "Either validation failed (`message` + `errors`) or the operation was refused on its own terms — a campaign already sending or sent, a locale with no body, an image that could not be fetched — which answers a single `error` string. Both shapes are 422.",
                "headers": {
                    "RateLimit-Policy": {
                        "$ref": "#/components/headers/RateLimit-Policy"
                    },
                    "RateLimit": {
                        "$ref": "#/components/headers/RateLimit"
                    },
                    "RateLimit-Limit": {
                        "$ref": "#/components/headers/RateLimit-Limit"
                    },
                    "RateLimit-Remaining": {
                        "$ref": "#/components/headers/RateLimit-Remaining"
                    },
                    "RateLimit-Reset": {
                        "$ref": "#/components/headers/RateLimit-Reset"
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "allOf": [
                                {
                                    "$ref": "#/components/schemas/Error"
                                },
                                {
                                    "type": "object",
                                    "properties": {
                                        "error": {
                                            "type": "string",
                                            "description": "Only on the second shape: one sentence saying why the operation was refused, in place of an `errors` map."
                                        }
                                    }
                                }
                            ]
                        },
                        "examples": {
                            "validation": {
                                "summary": "Validation failed",
                                "value": {
                                    "message": "The mode field is required.",
                                    "type": "https://publica.la/en/developers#error-validation_failed",
                                    "title": "Validation Failed",
                                    "status": 422,
                                    "detail": "The mode field is required.",
                                    "code": "validation_failed",
                                    "hint": "Read \"errors\" for the fields that failed and resend.",
                                    "docs": "https://publica.la/en/developers#errors",
                                    "errors": {
                                        "mode": [
                                            "The mode field is required."
                                        ]
                                    }
                                }
                            },
                            "refused": {
                                "summary": "Refused on its own terms",
                                "value": {
                                    "message": "Validation Failed",
                                    "type": "https://publica.la/en/developers#error-validation_failed",
                                    "title": "Validation Failed",
                                    "status": 422,
                                    "detail": "Validation Failed",
                                    "code": "validation_failed",
                                    "hint": "Read \"errors\" for the fields that failed and resend.",
                                    "docs": "https://publica.la/en/developers#errors",
                                    "error": "Campaign is already sent."
                                }
                            }
                        }
                    }
                }
            },
            "SendFailed": {
                "description": "The mailer refused the test send. The campaign is untouched. The body carries the shared error envelope **and** the legacy `error` sentence naming what the mailer said.",
                "headers": {
                    "RateLimit-Policy": {
                        "$ref": "#/components/headers/RateLimit-Policy"
                    },
                    "RateLimit": {
                        "$ref": "#/components/headers/RateLimit"
                    },
                    "RateLimit-Limit": {
                        "$ref": "#/components/headers/RateLimit-Limit"
                    },
                    "RateLimit-Remaining": {
                        "$ref": "#/components/headers/RateLimit-Remaining"
                    },
                    "RateLimit-Reset": {
                        "$ref": "#/components/headers/RateLimit-Reset"
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "allOf": [
                                {
                                    "$ref": "#/components/schemas/Error"
                                },
                                {
                                    "$ref": "#/components/schemas/LegacyError"
                                }
                            ]
                        },
                        "example": {
                            "message": "Internal Server Error",
                            "type": "https://publica.la/en/developers#error-server_error",
                            "title": "Internal Server Error",
                            "status": 500,
                            "detail": "Internal Server Error",
                            "code": "server_error",
                            "hint": "Retry with exponential backoff; if it persists, write to support@publica.la with the time of the request.",
                            "docs": "https://publica.la/en/developers#errors",
                            "error": "Send failed: Connection could not be established."
                        }
                    }
                }
            },
            "WidgetForbidden": {
                "description": "The widget token was missing or could not be verified, or the `Origin` is not on the allow-list (a write requires one; a read only requires that a present origin be allowed). A refusal deliberately carries **no** CORS headers, so a browser cannot read it — which is itself the signal.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "Invalid or expired widget token",
                            "type": "https://publica.la/en/developers#error-forbidden",
                            "title": "Forbidden",
                            "status": 403,
                            "detail": "Invalid or expired widget token",
                            "code": "forbidden",
                            "hint": "This endpoint is not the bearer API: send a valid X-Widget-Token (HMAC-SHA256 of {user_id}.{timestamp} under the shared secret) from an allow-listed Origin — see https://publica.la/en/developers",
                            "docs": "https://publica.la/en/developers#errors"
                        }
                    }
                }
            },
            "TooManyRequests": {
                "description": "The rate limit was hit. Wait `Retry-After` seconds before retrying — and never blind-retry a send, which is not reversible.",
                "headers": {
                    "RateLimit-Policy": {
                        "$ref": "#/components/headers/RateLimit-Policy"
                    },
                    "RateLimit": {
                        "$ref": "#/components/headers/RateLimit"
                    },
                    "RateLimit-Limit": {
                        "$ref": "#/components/headers/RateLimit-Limit"
                    },
                    "RateLimit-Remaining": {
                        "$ref": "#/components/headers/RateLimit-Remaining"
                    },
                    "RateLimit-Reset": {
                        "$ref": "#/components/headers/RateLimit-Reset"
                    },
                    "Retry-After": {
                        "$ref": "#/components/headers/Retry-After"
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        },
                        "example": {
                            "message": "Too Many Attempts.",
                            "type": "https://publica.la/en/developers#error-rate_limited",
                            "title": "Too Many Requests",
                            "status": 429,
                            "detail": "Too Many Attempts.",
                            "code": "rate_limited",
                            "hint": "Wait the number of seconds in Retry-After and retry; the quota is 300 requests per minute per key.",
                            "docs": "https://publica.la/en/developers#errors"
                        }
                    }
                }
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "description": "The error envelope. `message` (and `errors` on a 422) is the original contract and is always present; the RFC 9457 problem members sit beside it, so a client can read either without content negotiation.",
                "required": [
                    "message"
                ],
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "Human-readable summary. The original, unchanging field."
                    },
                    "errors": {
                        "type": "object",
                        "description": "Only on a 422: each rejected field mapped to its messages.",
                        "additionalProperties": {
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    },
                    "required_scope": {
                        "type": "string",
                        "enum": [
                            "content.read",
                            "content.write",
                            "newsletter.read",
                            "newsletter.write",
                            "newsletter.send",
                            "subscribers.write",
                            "suppressions.write",
                            "scheduling.read",
                            "scheduling.write"
                        ],
                        "description": "Only on a 403: the scope this route needs and the key does not carry."
                    },
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "description": "RFC 9457: a URI identifying the problem type."
                    },
                    "title": {
                        "type": "string",
                        "description": "RFC 9457: a short, stable summary of the problem type."
                    },
                    "status": {
                        "type": "integer",
                        "description": "RFC 9457: the HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "RFC 9457: what went wrong with this specific request."
                    },
                    "code": {
                        "type": "string",
                        "description": "A stable machine-readable code for this failure."
                    },
                    "hint": {
                        "type": "string",
                        "description": "What to do about it — written for an agent deciding whether to retry."
                    },
                    "docs": {
                        "type": "string",
                        "format": "uri",
                        "description": "The page that explains this failure."
                    }
                }
            },
            "LegacyError": {
                "type": "object",
                "description": "The older refusal shape a few endpoints still answer with: a single `error` sentence, no `errors` map. Kept because live consumers read it.",
                "required": [
                    "error"
                ],
                "properties": {
                    "error": {
                        "type": "string",
                        "description": "Why the operation was refused."
                    }
                }
            },
            "PaginationLinks": {
                "type": "object",
                "description": "The paginator's own links. `prev` and `next` are null at the ends.",
                "properties": {
                    "first": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "last": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "prev": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "next": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    }
                }
            },
            "PaginationMeta": {
                "type": "object",
                "description": "Where in the collection this page sits.",
                "properties": {
                    "current_page": {
                        "type": "integer"
                    },
                    "from": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "last_page": {
                        "type": "integer"
                    },
                    "path": {
                        "type": "string",
                        "format": "uri"
                    },
                    "per_page": {
                        "type": "integer"
                    },
                    "to": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "total": {
                        "type": "integer"
                    },
                    "links": {
                        "type": "array",
                        "description": "Ready-made pagination links, as the front end renders them.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "url": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "uri"
                                },
                                "label": {
                                    "type": "string"
                                },
                                "active": {
                                    "type": "boolean"
                                }
                            }
                        }
                    }
                }
            },
            "TranslatedText": {
                "type": "object",
                "description": "A translatable field: locale to text, over en, es, pt, fr, de, it, pl. A locale that was never written is absent rather than empty.",
                "propertyNames": {
                    "enum": [
                        "en",
                        "es",
                        "pt",
                        "fr",
                        "de",
                        "it",
                        "pl"
                    ]
                },
                "additionalProperties": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "example": {
                    "en": "Reader accessibility",
                    "es": "Accesibilidad del lector"
                }
            },
            "SlackResult": {
                "type": "object",
                "description": "Slack's own `chat.postMessage` reply, passed through untouched — `{\"ok\": true, \"channel\": …, \"ts\": …}` on success, `{\"ok\": false, \"error\": …}` on a refusal Slack answers 200 to. An empty object means no Slack channel is configured here, which is a normal state and not an error.",
                "additionalProperties": true,
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "channel": {
                        "type": "string"
                    },
                    "ts": {
                        "type": "string"
                    },
                    "error": {
                        "type": "string"
                    }
                }
            },
            "SiteMeta": {
                "type": "object",
                "description": "Which language the answer is in and when the origin generated it. These answers are edge-cacheable for an hour, so `generated_at` is how old the copy in hand may be.",
                "required": [
                    "locale",
                    "generated_at"
                ],
                "properties": {
                    "locale": {
                        "type": "string",
                        "enum": [
                            "en",
                            "es",
                            "pt",
                            "fr",
                            "de",
                            "it",
                            "pl"
                        ],
                        "description": "The language this answer is in."
                    },
                    "generated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the origin built this answer, ISO 8601."
                    },
                    "count": {
                        "type": "integer",
                        "description": "On a list: how many rows are in `data`."
                    },
                    "limit": {
                        "type": "integer",
                        "description": "On a list: the cap in force. `count === limit` means there may be more."
                    }
                }
            },
            "SitePost": {
                "type": "object",
                "description": "One published blog post in one language. `body` is present only when a single post was fetched, and is plain text — the HTML is stripped, so it is quotable as it stands.",
                "required": [
                    "id",
                    "slug",
                    "title",
                    "summary",
                    "category",
                    "url"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "The post's numeric id."
                    },
                    "slug": {
                        "type": "string",
                        "description": "The slug in the blog URL, and what `GET /site/posts/{slug}` takes."
                    },
                    "title": {
                        "type": "string",
                        "description": "The headline, in the requested language."
                    },
                    "summary": {
                        "type": "string",
                        "description": "The standfirst — a paragraph, in the requested language."
                    },
                    "body": {
                        "type": "string",
                        "description": "The article as plain text. Only on a single-post read."
                    },
                    "category": {
                        "type": "string",
                        "enum": [
                            "product-updates",
                            "guides",
                            "case-studies",
                            "publishing-insights",
                            "industry"
                        ],
                        "description": "Which kind of post it is."
                    },
                    "pillar": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "publishers",
                            "bookshops",
                            "libraries",
                            "magazines",
                            "university-presses",
                            "authors",
                            null
                        ],
                        "description": "Which audience it is written for. Null on a product update."
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When it went live."
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Where a person reads it."
                    }
                }
            },
            "SiteChangelogEntry": {
                "type": "object",
                "description": "One published changelog entry — a month of the product's history, grouped into sections.",
                "required": [
                    "id",
                    "month",
                    "period",
                    "sections",
                    "url"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "month": {
                        "type": "string",
                        "description": "The month as a person would read it, in the requested language — \"August 2026\"."
                    },
                    "period": {
                        "type": "string",
                        "description": "The same month as `YYYY-MM`, for sorting and comparing."
                    },
                    "date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "The first day of the month the entry covers."
                    },
                    "sections": {
                        "type": "object",
                        "description": "Section name (Added, Changed, Deprecated, Removed, Fixed, Security) to the blocks under it, each `{heading, items}`.",
                        "additionalProperties": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "heading": {
                                        "type": "string"
                                    },
                                    "items": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The changelog page this entry is on."
                    }
                }
            },
            "SitePricing": {
                "type": "object",
                "description": "The plans as the pricing page states them. Read from the same language files the page renders, so the two cannot disagree.",
                "required": [
                    "currency",
                    "plans",
                    "url"
                ],
                "properties": {
                    "currency": {
                        "type": "string",
                        "const": "USD",
                        "description": "Every price on this API is in US dollars."
                    },
                    "plans": {
                        "type": "array",
                        "description": "Starter, Growth and Enterprise, in that order.",
                        "items": {
                            "type": "object",
                            "required": [
                                "key",
                                "name",
                                "monthly_price"
                            ],
                            "properties": {
                                "key": {
                                    "type": "string",
                                    "description": "A stable identifier — `starter`, `growth`, `enterprise`."
                                },
                                "name": {
                                    "type": "string"
                                },
                                "description": {
                                    "type": "string",
                                    "description": "Who the plan is for."
                                },
                                "monthly_price": {
                                    "type": "string",
                                    "description": "As written, including the currency symbol and any \"From\" — `$20`, `From $492`."
                                },
                                "annual_price": {
                                    "type": "string",
                                    "description": "The same plan paid yearly."
                                },
                                "key_features": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    },
                                    "description": "The two lines the pricing card leads with."
                                },
                                "features": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    },
                                    "description": "The full list under them."
                                }
                            }
                        }
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The pricing page, in the requested language."
                    }
                }
            },
            "SiteOverview": {
                "type": "object",
                "description": "What publica.la is, and — as importantly — what it is not for. `not_for` exists because the commonest wrong answer an agent gives about a platform is recommending it for something it does not do.",
                "required": [
                    "name",
                    "tagline",
                    "description",
                    "url",
                    "when_to_use",
                    "not_for"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "const": "publica.la"
                    },
                    "tagline": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string",
                        "description": "A paragraph: the whole of what the platform does."
                    },
                    "locale": {
                        "type": "string",
                        "enum": [
                            "en",
                            "es",
                            "pt",
                            "fr",
                            "de",
                            "it",
                            "pl"
                        ],
                        "description": "The language the URLs below are in. The prose itself is English — inventing a translation of \"what publica.la is not for\" would put claims nobody wrote in front of an agent."
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The site's home page in that language."
                    },
                    "when_to_use": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The cases publica.la is the right answer for."
                    },
                    "not_for": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The cases it is not, said plainly."
                    },
                    "solutions": {
                        "type": "array",
                        "description": "The audience pages, each with its URL.",
                        "items": {
                            "type": "object",
                            "required": [
                                "name",
                                "url"
                            ],
                            "properties": {
                                "name": {
                                    "type": "string"
                                },
                                "url": {
                                    "type": "string",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "features": {
                        "type": "array",
                        "description": "The feature pages, each with its URL.",
                        "items": {
                            "type": "object",
                            "required": [
                                "name",
                                "url"
                            ],
                            "properties": {
                                "name": {
                                    "type": "string"
                                },
                                "url": {
                                    "type": "string",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "contacts": {
                        "type": "object",
                        "description": "Who to write to. There is no phone number and no street address on this API.",
                        "additionalProperties": {
                            "type": "string"
                        }
                    },
                    "machine_entry_points": {
                        "type": "object",
                        "description": "Every other machine-readable door: `llms.txt`, this description, the developer portal, the MCP server.",
                        "additionalProperties": {
                            "type": "string"
                        }
                    }
                }
            },
            "Post": {
                "type": "object",
                "description": "A blog post. One table backs the whole blog; `category` says which kind of post it is.",
                "required": [
                    "id",
                    "slug",
                    "category",
                    "title"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "slug": {
                        "type": "string",
                        "description": "The URL slug. Published at `/{locale}/blog/{slug}`."
                    },
                    "category": {
                        "type": "string",
                        "enum": [
                            "product-updates",
                            "guides",
                            "case-studies",
                            "publishing-insights",
                            "industry"
                        ]
                    },
                    "pillar": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "publishers",
                            "bookshops",
                            "libraries",
                            "magazines",
                            "university-presses",
                            "authors",
                            null
                        ]
                    },
                    "subcategory": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "feature",
                            "announcement",
                            "improvement",
                            null
                        ]
                    },
                    "version": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "author_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64"
                    },
                    "author": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/PostAuthor"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The author, loaded on the list, create, read and update operations. **Absent** — not null — from the answers to `publish` and `cover-image`, which do not load the relation; read the post back if you need it there."
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Date only, not a timestamp."
                    },
                    "is_published": {
                        "type": "boolean"
                    },
                    "cover_image": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "og_image": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "title": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "summary": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "body": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "seo_title": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "seo_description": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "PostAuthor": {
                "type": "object",
                "description": "The post's author, as the blog shows them.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "slug": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "avatar": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "role": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "bio": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "twitter_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "linkedin_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "website_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email"
                    }
                }
            },
            "ChangelogEntry": {
                "type": "object",
                "description": "One month of the changelog.",
                "required": [
                    "id",
                    "sections"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "The month this entry covers."
                    },
                    "sections": {
                        "type": "object",
                        "description": "Locale to section name (Added, Changed, Fixed…) to its `{heading, items}` blocks.",
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "object",
                            "additionalProperties": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/components/schemas/ChangelogBlock"
                                }
                            }
                        },
                        "example": {
                            "en": {
                                "Added": [
                                    {
                                        "heading": "Footnotes",
                                        "items": [
                                            "In-place footnotes in the reader"
                                        ]
                                    }
                                ]
                            }
                        }
                    },
                    "is_published": {
                        "type": "boolean"
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "ChangelogBlock": {
                "type": "object",
                "description": "One block inside a changelog section: a heading and the lines under it.",
                "required": [
                    "heading",
                    "items"
                ],
                "properties": {
                    "heading": {
                        "type": "string",
                        "description": "Translated per locale, because the block lives inside a locale."
                    },
                    "items": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                            "type": "string"
                        }
                    }
                }
            },
            "NewsletterCampaign": {
                "type": "object",
                "description": "A newsletter edition — sent once to the whole sendable list, segmented by each contact's locale.",
                "required": [
                    "id",
                    "slug",
                    "status"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "slug": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "draft",
                            "scheduled",
                            "sending",
                            "sent",
                            "cancelled"
                        ],
                        "description": "`sending` and `sent` are locked: the edition can no longer be edited or deleted."
                    },
                    "from_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "from_email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email"
                    },
                    "subject": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "preheader": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "body_html": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "scheduled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "sent_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "stats": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/NewsletterCampaignStats"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Present when a single campaign is read, absent from the list."
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "NewsletterCampaignStats": {
                "type": "object",
                "description": "Per-campaign counters, aggregated from its send records.",
                "properties": {
                    "total": {
                        "type": "integer",
                        "description": "Send records written for this campaign."
                    },
                    "sent": {
                        "type": "integer",
                        "description": "Handed to the mailer."
                    },
                    "failed": {
                        "type": "integer",
                        "description": "The mailer refused."
                    },
                    "skipped": {
                        "type": "integer",
                        "description": "Skipped — suppressed or unsubscribed before the send reached them."
                    },
                    "delivered": {
                        "type": "integer",
                        "description": "The provider confirmed delivery."
                    },
                    "opened": {
                        "type": "integer",
                        "description": "Opened at least once."
                    },
                    "clicked": {
                        "type": "integer",
                        "description": "A link was clicked."
                    },
                    "bounced": {
                        "type": "integer",
                        "description": "Bounced."
                    },
                    "complained": {
                        "type": "integer",
                        "description": "Marked as spam."
                    }
                }
            },
            "NewsletterAsset": {
                "type": "object",
                "description": "An image stored for use inside an edition's HTML. The original, never resized.",
                "required": [
                    "id",
                    "url"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "filename": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The name it arrived under, kept for humans. Not the stored path."
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The public URL to embed."
                    },
                    "mime": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "size": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Bytes."
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "Subscriber": {
                "type": "object",
                "description": "A newsletter contact. Deliberately minimal: an address, the language it reads, and when it opted in or out.",
                "required": [
                    "id",
                    "email"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "locale": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "en",
                            "es",
                            "pt",
                            "fr",
                            "de",
                            "it",
                            "pl",
                            null
                        ]
                    },
                    "active": {
                        "type": "boolean",
                        "description": "True while `unsubscribed_at` is null. Suppression is a separate list and is not visible here."
                    },
                    "subscribed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "unsubscribed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "SubscriberEnvelope": {
                "type": "object",
                "description": "The upsert answer: the contact, plus whether the address is on the suppression list. A suppressed address is never reactivated, so `suppressed: true` with `active: false` is the ingestion working as intended.",
                "required": [
                    "data",
                    "suppressed"
                ],
                "properties": {
                    "data": {
                        "$ref": "#/components/schemas/Subscriber"
                    },
                    "suppressed": {
                        "type": "boolean",
                        "description": "True when the address is suppressed and was therefore left opted out."
                    }
                }
            },
            "NewsletterStats": {
                "type": "object",
                "description": "The newsletter module's read-only analytics. Not wrapped in `data`.",
                "required": [
                    "audience",
                    "campaigns"
                ],
                "properties": {
                    "audience": {
                        "type": "object",
                        "required": [
                            "subscribers",
                            "suppressions",
                            "sendable",
                            "origami_users",
                            "by_locale",
                            "by_email_language"
                        ],
                        "properties": {
                            "subscribers": {
                                "type": "object",
                                "properties": {
                                    "total": {
                                        "type": "integer"
                                    },
                                    "active": {
                                        "type": "integer",
                                        "description": "Not unsubscribed. Suppression is counted separately."
                                    },
                                    "unsubscribed": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "suppressions": {
                                "type": "integer",
                                "description": "Rows on the suppression list, which is not limited to subscribers."
                            },
                            "sendable": {
                                "type": "integer",
                                "description": "Active **and** not suppressed — who a send would actually reach."
                            },
                            "origami_users": {
                                "type": "integer",
                                "description": "Sendable contacts tagged as Origami users."
                            },
                            "by_locale": {
                                "type": "object",
                                "description": "Sendable contacts per stored locale, largest first.",
                                "additionalProperties": {
                                    "type": "integer"
                                }
                            },
                            "by_email_language": {
                                "type": "object",
                                "description": "What each contact would actually receive once the send falls back: Spanish for `es`, English for everyone else.",
                                "properties": {
                                    "es": {
                                        "type": "integer"
                                    },
                                    "en": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    },
                    "campaigns": {
                        "type": "array",
                        "description": "The most recent editions, newest first.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer",
                                    "format": "int64"
                                },
                                "slug": {
                                    "type": "string"
                                },
                                "status": {
                                    "type": "string",
                                    "enum": [
                                        "draft",
                                        "scheduled",
                                        "sending",
                                        "sent",
                                        "cancelled"
                                    ]
                                },
                                "subject": {
                                    "$ref": "#/components/schemas/TranslatedText"
                                },
                                "scheduled_at": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date-time"
                                },
                                "sent_at": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date-time"
                                },
                                "sends": {
                                    "$ref": "#/components/schemas/NewsletterCampaignStats"
                                },
                                "rates": {
                                    "type": "object",
                                    "description": "Percentages, one decimal place. Each is 0 where its denominator is 0 — read a rate of 0 alongside `sends`, never on its own.",
                                    "properties": {
                                        "delivery": {
                                            "type": "number",
                                            "description": "delivered ÷ sent."
                                        },
                                        "open": {
                                            "type": "number",
                                            "description": "opened ÷ delivered."
                                        },
                                        "click": {
                                            "type": "number",
                                            "description": "clicked ÷ delivered."
                                        },
                                        "bounce": {
                                            "type": "number",
                                            "description": "bounced ÷ sent."
                                        },
                                        "complaint": {
                                            "type": "number",
                                            "description": "complained ÷ delivered."
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "EventType": {
                "type": "object",
                "description": "A booking page: the row, plus the three things only the application can answer — which URLs it responds on, whether it is really taking bookings, and who is in the pool.",
                "required": [
                    "id",
                    "slug",
                    "kind",
                    "name"
                ],
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "slug": {
                        "type": "string"
                    },
                    "kind": {
                        "type": "string",
                        "enum": [
                            "personal",
                            "catalogue"
                        ],
                        "description": "Derived from `owner_id`: `personal` is booked at `/meet/{slug}`, `catalogue` at `/book/{slug}`."
                    },
                    "owner_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64"
                    },
                    "is_active": {
                        "type": "boolean"
                    },
                    "name": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "description": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "scheduling_type": {
                        "type": "string",
                        "enum": [
                            "personal",
                            "round_robin",
                            "collective",
                            "group"
                        ]
                    },
                    "duration_minutes": {
                        "type": "integer"
                    },
                    "duration_options": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "integer"
                        }
                    },
                    "slot_interval_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "buffer_before_minutes": {
                        "type": "integer"
                    },
                    "buffer_after_minutes": {
                        "type": "integer"
                    },
                    "minimum_notice_minutes": {
                        "type": "integer"
                    },
                    "daily_limit": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "seats_per_slot": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "language_source": {
                        "type": "string",
                        "enum": [
                            "listed",
                            "hosts"
                        ]
                    },
                    "offered_languages": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The list as stored, so a round-trip is lossless."
                    },
                    "languages_on_offer": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "What the page actually offers today. Differs from `offered_languages` on a page reading its languages off its hosts."
                    },
                    "location_type": {
                        "type": "string",
                        "enum": [
                            "google_meet",
                            "in_person"
                        ]
                    },
                    "location": {
                        "$ref": "#/components/schemas/TranslatedText"
                    },
                    "hours_source": {
                        "type": "string",
                        "enum": [
                            "page",
                            "hosts"
                        ]
                    },
                    "weekly_hours": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/WeeklyHourWindow"
                        },
                        "description": "Normalised and sorted."
                    },
                    "event_hours": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/EventHourWindow"
                        },
                        "description": "Normalised and sorted; rows the slot engine would have dropped are already gone."
                    },
                    "available_from": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "available_until": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "collect_details_first": {
                        "type": "boolean",
                        "description": "The stored flag, so a round-trip is lossless."
                    },
                    "collects_details_first": {
                        "type": "boolean",
                        "description": "What the page actually does. An event calendar always shows its times first, whatever the flag says."
                    },
                    "questions": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/QuestionDefinition"
                        }
                    },
                    "brand_display": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "photo",
                            "logo",
                            "none",
                            null
                        ]
                    },
                    "public_urls": {
                        "type": "object",
                        "description": "Locale to the URL the page answers on. Only a language somebody actually wrote a name in gets a link.",
                        "additionalProperties": {
                            "type": "string",
                            "format": "uri"
                        }
                    },
                    "is_live": {
                        "type": "boolean",
                        "description": "Whether handing those URLs out is a promise the page can keep: switched on, inside its dates, with at least one host whose calendar is connected."
                    },
                    "hosts": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/EventTypeHost"
                        },
                        "description": "The pool. Present on every endpoint here."
                    },
                    "bookings_count": {
                        "type": "integer",
                        "description": "Present when the count was loaded — on read, and it is what makes `DELETE` deactivate instead of delete."
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "EventTypeHost": {
                "type": "object",
                "description": "A host in the pool.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64"
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email"
                    },
                    "weight": {
                        "type": "integer",
                        "minimum": 1,
                        "description": "Round-robin weight. Anything missing or unreadable reads as 1 — a weight of 0 would drop the host out of the rotation entirely."
                    },
                    "spoken_languages": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The languages this host can hold a meeting in."
                    },
                    "calendar_connected": {
                        "type": "boolean",
                        "description": "Whether their calendar is connected, which is what makes them bookable at all."
                    }
                }
            },
            "EventTypeDeactivated": {
                "type": "object",
                "description": "The answer when a `DELETE` hit a page that has bookings: it was switched off instead, because bookings cascade from it.",
                "required": [
                    "deleted",
                    "deactivated",
                    "message"
                ],
                "properties": {
                    "deleted": {
                        "type": "boolean",
                        "const": false
                    },
                    "deactivated": {
                        "type": "boolean",
                        "const": true
                    },
                    "message": {
                        "type": "string"
                    }
                }
            },
            "WeeklyHourWindow": {
                "type": "object",
                "description": "One window of one weekday. A window that ends before it starts, or overlaps the one above it, is refused rather than silently dropped.",
                "required": [
                    "day",
                    "start",
                    "end"
                ],
                "properties": {
                    "day": {
                        "type": "string",
                        "enum": [
                            "monday",
                            "tuesday",
                            "wednesday",
                            "thursday",
                            "friday",
                            "saturday",
                            "sunday"
                        ]
                    },
                    "start": {
                        "type": "string",
                        "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
                        "description": "`HH:MM`, read on the clock the page resolves to."
                    },
                    "end": {
                        "type": "string",
                        "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
                        "description": "`HH:MM`, after `start`."
                    }
                }
            },
            "EventHourWindow": {
                "type": "object",
                "description": "Opening hours for an event. A row with a `date` replaces the undated rows for that date; an undated row is the ordinary day.",
                "required": [
                    "start",
                    "end"
                ],
                "properties": {
                    "date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "start": {
                        "type": "string",
                        "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$"
                    },
                    "end": {
                        "type": "string",
                        "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$"
                    }
                }
            },
            "QuestionDefinition": {
                "type": "object",
                "description": "One question the booking form asks on top of first name, last name and email — which are always asked.",
                "required": [
                    "key",
                    "label"
                ],
                "properties": {
                    "key": {
                        "type": "string",
                        "maxLength": 50,
                        "pattern": "^[A-Za-z0-9_-]+$",
                        "description": "Stored on the booking under this key, and prefillable from the URL as `?{key}=…`."
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "text",
                            "textarea",
                            "select",
                            "phone"
                        ],
                        "default": "text"
                    },
                    "required": {
                        "type": "boolean",
                        "default": false
                    },
                    "label": {
                        "type": "object",
                        "description": "What the visitor reads. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were. English (`en`) is what every other locale falls back to, so it has to be there.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 200
                        },
                        "required": [
                            "en"
                        ]
                    },
                    "options": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "description": "Only for `select`. The **value** is what is stored and what validation accepts; the label is only what is shown.",
                        "items": {
                            "type": "object",
                            "required": [
                                "value",
                                "label"
                            ],
                            "properties": {
                                "value": {
                                    "type": "string",
                                    "maxLength": 100
                                },
                                "label": {
                                    "type": "object",
                                    "description": "What the visitor reads for this option. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were. English (`en`) is what every other locale falls back to, so it has to be there.",
                                    "minProperties": 1,
                                    "propertyNames": {
                                        "enum": [
                                            "en",
                                            "es",
                                            "pt",
                                            "fr",
                                            "de",
                                            "it",
                                            "pl"
                                        ]
                                    },
                                    "additionalProperties": {
                                        "type": "string",
                                        "maxLength": 200
                                    },
                                    "required": [
                                        "en"
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            "WidgetStatus": {
                "type": "object",
                "description": "What the notification widget shows one user. Only published `product-updates` posts are ever counted or listed.",
                "required": [
                    "has_unseen",
                    "unseen_count",
                    "latest",
                    "releases"
                ],
                "properties": {
                    "has_unseen": {
                        "type": "boolean"
                    },
                    "unseen_count": {
                        "type": "integer"
                    },
                    "site_url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The site root the URLs below were built from. Absent when there are no posts at all."
                    },
                    "latest": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/WidgetRelease"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The most recent post, or null when there are none."
                    },
                    "releases": {
                        "type": "array",
                        "maxItems": 5,
                        "items": {
                            "$ref": "#/components/schemas/WidgetRelease"
                        },
                        "description": "The five most recent — always returned, even once everything is seen, so the bell can always open the widget."
                    }
                }
            },
            "WidgetRelease": {
                "type": "object",
                "description": "One product update, rendered for the requested locale with an English fallback.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Pass this back as `release_id` to `POST /widget/seen`."
                    },
                    "slug": {
                        "type": "string"
                    },
                    "version": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "cover_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri",
                        "description": "Absolute URL."
                    },
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The post on the blog, in the requested locale — `/{locale}/blog/{slug}`."
                    },
                    "title": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "summary": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                }
            },
            "WidgetOk": {
                "type": "object",
                "description": "The write landed.",
                "required": [
                    "ok"
                ],
                "properties": {
                    "ok": {
                        "type": "boolean",
                        "const": true
                    }
                }
            },
            "PostCreate": {
                "type": "object",
                "required": [
                    "title"
                ],
                "additionalProperties": false,
                "description": "A new post. Only `title` is required; everything else has a default or is optional.",
                "properties": {
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "URL slug, unique across posts. Generated from the English title when omitted."
                    },
                    "category": {
                        "type": "string",
                        "enum": [
                            "product-updates",
                            "guides",
                            "case-studies",
                            "publishing-insights",
                            "industry"
                        ],
                        "default": "product-updates",
                        "description": "Which kind of post this is. Omitting it keeps the database default, `product-updates` — existing consumers depend on that."
                    },
                    "pillar": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "publishers",
                            "bookshops",
                            "libraries",
                            "magazines",
                            "university-presses",
                            "authors",
                            null
                        ],
                        "description": "Which audience the post is written for. Product updates carry none."
                    },
                    "subcategory": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "feature",
                            "announcement",
                            "improvement",
                            null
                        ],
                        "description": "Only meaningful on `product-updates`."
                    },
                    "version": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Platform version this update belongs to. Only meaningful on `product-updates`."
                    },
                    "author_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64",
                        "description": "An existing author's id. Mutually exclusive with `author_email`."
                    },
                    "author_email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email",
                        "description": "An existing author's email — human-friendly and stable across environments; it is resolved to `author_id`. Sending both is a 422 unless they agree."
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the post goes (or went) live."
                    },
                    "is_published": {
                        "type": "boolean",
                        "default": false,
                        "description": "Leave false to keep it a draft; `POST /posts/{post}/publish` is the other way to flip it."
                    },
                    "cover_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 500,
                        "description": "Cover image URL or path. `POST /posts/{post}/cover-image` sets it from a remote image instead."
                    },
                    "og_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 500,
                        "description": "Social preview image. Falls back to the cover."
                    },
                    "title": {
                        "type": "object",
                        "description": "The post title. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 500
                        }
                    },
                    "summary": {
                        "type": "object",
                        "description": "One-paragraph summary, shown in listings and feeds. Links to login-gated publica.la hosts are refused. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 1000
                        }
                    },
                    "body": {
                        "type": "object",
                        "description": "The post body (HTML). Links to login-gated publica.la hosts are refused. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ]
                        }
                    },
                    "seo_title": {
                        "type": "object",
                        "description": "SEO title. Keep it to 60 characters. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    "seo_description": {
                        "type": "object",
                        "description": "SEO meta description. Keep it to 160 characters. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 500
                        }
                    }
                }
            },
            "PostUpdate": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "description": "The fields to change. Every one is optional, and `slug` cannot be changed here.",
                "properties": {
                    "category": {
                        "type": "string",
                        "enum": [
                            "product-updates",
                            "guides",
                            "case-studies",
                            "publishing-insights",
                            "industry"
                        ],
                        "default": "product-updates",
                        "description": "Which kind of post this is. Omitting it keeps the database default, `product-updates` — existing consumers depend on that."
                    },
                    "pillar": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "publishers",
                            "bookshops",
                            "libraries",
                            "magazines",
                            "university-presses",
                            "authors",
                            null
                        ],
                        "description": "Which audience the post is written for. Product updates carry none."
                    },
                    "subcategory": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "feature",
                            "announcement",
                            "improvement",
                            null
                        ],
                        "description": "Only meaningful on `product-updates`."
                    },
                    "version": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Platform version this update belongs to. Only meaningful on `product-updates`."
                    },
                    "author_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64",
                        "description": "An existing author's id. Mutually exclusive with `author_email`."
                    },
                    "author_email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email",
                        "description": "An existing author's email — human-friendly and stable across environments; it is resolved to `author_id`. Sending both is a 422 unless they agree."
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the post goes (or went) live."
                    },
                    "is_published": {
                        "type": "boolean",
                        "default": false,
                        "description": "Leave false to keep it a draft; `POST /posts/{post}/publish` is the other way to flip it."
                    },
                    "cover_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 500,
                        "description": "Cover image URL or path. `POST /posts/{post}/cover-image` sets it from a remote image instead."
                    },
                    "og_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 500,
                        "description": "Social preview image. Falls back to the cover."
                    },
                    "title": {
                        "type": "object",
                        "description": "The post title. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 500
                        }
                    },
                    "summary": {
                        "type": "object",
                        "description": "One-paragraph summary, shown in listings and feeds. Links to login-gated publica.la hosts are refused. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 1000
                        }
                    },
                    "body": {
                        "type": "object",
                        "description": "The post body (HTML). Links to login-gated publica.la hosts are refused. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ]
                        }
                    },
                    "seo_title": {
                        "type": "object",
                        "description": "SEO title. Keep it to 60 characters. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    "seo_description": {
                        "type": "object",
                        "description": "SEO meta description. Keep it to 160 characters. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 500
                        }
                    }
                }
            },
            "ChangelogEntryCreate": {
                "type": "object",
                "required": [
                    "date",
                    "sections"
                ],
                "additionalProperties": false,
                "description": "One entry per month. `sections` is a locale map of section name (Added, Changed, Deprecated, Removed, Fixed, Security) to a list of `{heading, items}` blocks.",
                "properties": {
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The month this entry covers — any date inside it."
                    },
                    "sections": {
                        "type": "object",
                        "description": "Keyed by locale (en, es, pt, fr, de, it, pl), then by section name. Sending one locale replaces that locale and leaves the others alone.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "object",
                            "description": "Section name (Added, Changed, Fixed…) to its blocks.",
                            "additionalProperties": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/components/schemas/ChangelogBlock"
                                }
                            }
                        }
                    },
                    "is_published": {
                        "type": "boolean",
                        "default": false
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "ChangelogEntryUpdate": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "description": "One entry per month. `sections` is a locale map of section name (Added, Changed, Deprecated, Removed, Fixed, Security) to a list of `{heading, items}` blocks.",
                "properties": {
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The month this entry covers — any date inside it."
                    },
                    "sections": {
                        "type": "object",
                        "description": "Keyed by locale (en, es, pt, fr, de, it, pl), then by section name. Sending one locale replaces that locale and leaves the others alone.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "object",
                            "description": "Section name (Added, Changed, Fixed…) to its blocks.",
                            "additionalProperties": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/components/schemas/ChangelogBlock"
                                }
                            }
                        }
                    },
                    "is_published": {
                        "type": "boolean",
                        "default": false
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "EventTypeCreate": {
                "type": "object",
                "required": [
                    "slug",
                    "name"
                ],
                "additionalProperties": false,
                "description": "A new booking page. `slug` and an English `name` are required; everything else has a default.",
                "properties": {
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "pattern": "^[A-Za-z0-9_-]+$",
                        "description": "The URL slug, unique across personal and catalogue pages alike — a slug names exactly one meeting."
                    },
                    "name": {
                        "type": "object",
                        "description": "The meeting name, as the visitor reads it. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were. English (`en`) is what every other locale falls back to, so it has to be there.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 150
                        },
                        "required": [
                            "en"
                        ]
                    },
                    "description": {
                        "type": "object",
                        "description": "One line under the name. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 300
                        }
                    },
                    "owner_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64",
                        "description": "Set it and the page is that host's own personal meeting (`/meet/{slug}`); leave it null and it is the organisation's catalogue (`/book/{slug}`)."
                    },
                    "is_active": {
                        "type": "boolean",
                        "description": "Off takes the page out of every surface without deleting it."
                    },
                    "scheduling_type": {
                        "type": "string",
                        "enum": [
                            "personal",
                            "round_robin",
                            "collective",
                            "group"
                        ],
                        "description": "`round_robin` routes to the least-booked host; `personal` is one host; `collective` needs every host free at once; `group` shares a slot between attendees."
                    },
                    "brand_display": {
                        "type": "string",
                        "enum": [
                            "photo",
                            "logo",
                            "none"
                        ],
                        "description": "What the standalone page puts above the card. A catalogue page only takes `logo` or `none` — a face belongs to a person."
                    },
                    "duration_minutes": {
                        "type": "integer",
                        "minimum": 5,
                        "maximum": 480,
                        "description": "The default length, always offered."
                    },
                    "duration_options": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "integer",
                            "minimum": 5,
                            "maximum": 480
                        },
                        "description": "Extra lengths offered on top of the default. Null or empty means one length and no choice shown."
                    },
                    "slot_interval_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 5,
                        "maximum": 480,
                        "description": "How often a slot starts, aligned to the clock. Null steps by the duration."
                    },
                    "buffer_before_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 120,
                        "description": "Time kept clear before a booking. Buffers pad bookings; they do not thin out the grid."
                    },
                    "buffer_after_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 120,
                        "description": "Time kept clear after a booking."
                    },
                    "minimum_notice_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "How far ahead a visitor must book."
                    },
                    "daily_limit": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 1,
                        "description": "Most bookings per day. Null is unlimited."
                    },
                    "seats_per_slot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 2,
                        "description": "Only meaningful for `group`. Null is unlimited."
                    },
                    "offered_languages": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "description": "The languages the meeting can be held in. Kept whichever `language_source` is in force, so switching back and forth loses nothing. Declaring none means every language."
                    },
                    "language_source": {
                        "type": "string",
                        "enum": [
                            "listed",
                            "hosts"
                        ],
                        "description": "`listed` offers exactly the list above; `hosts` offers the union of what the bookable pool has declared, so a language is never promised without somebody who can hold the meeting in it."
                    },
                    "location_type": {
                        "type": "string",
                        "enum": [
                            "google_meet",
                            "in_person"
                        ],
                        "description": "`in_person` replaces the video link with the address everywhere — page, calendar event, emails, `.ics`."
                    },
                    "location": {
                        "type": "object",
                        "description": "The address, when the meeting happens somewhere rather than in a video room. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 200
                        }
                    },
                    "hours_source": {
                        "type": "string",
                        "enum": [
                            "page",
                            "hosts"
                        ],
                        "description": "`page` slices the week written below; `hosts` reads each host's own week, on their own clock, falling back to the page's for a host who has not written one."
                    },
                    "weekly_hours": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/WeeklyHourWindow"
                        },
                        "description": "The week the page is open. In `hosts` mode this stays as the per-host fallback."
                    },
                    "event_hours": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/EventHourWindow"
                        },
                        "description": "Opening hours for a real event, overriding `weekly_hours` on the dates they cover. An undated row is the ordinary day. Setting any of these **requires** `timezone`."
                    },
                    "available_from": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First bookable date. Together with `available_until` this pins the page to a real event and overrides the rolling horizon."
                    },
                    "available_until": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last bookable date. Must not precede `available_from`."
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "An IANA timezone. Required once `event_hours` are set: one clock for everyone, or the same \"10:00\" lands at a different instant per host."
                    },
                    "collect_details_first": {
                        "type": "boolean",
                        "description": "Ask the questions before showing the times. Ignored on a page with event dates — those always show the times first."
                    },
                    "questions": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/QuestionDefinition"
                        },
                        "description": "What the booking form asks on top of first name, last name and email, which are always asked."
                    },
                    "host_ids": {
                        "type": "array",
                        "items": {
                            "type": "integer",
                            "format": "int64"
                        },
                        "description": "The host pool, as user ids. Not a column — it is synced as a relation. Omit on an update to leave the pool alone; on a personal meeting it is overruled back to the owner."
                    }
                }
            },
            "EventTypeUpdate": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "description": "The fields to change. Every one is optional.",
                "properties": {
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "pattern": "^[A-Za-z0-9_-]+$",
                        "description": "The URL slug. Must stay unique against every other page."
                    },
                    "name": {
                        "type": "object",
                        "description": "The meeting name, as the visitor reads it. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 150
                        }
                    },
                    "description": {
                        "type": "object",
                        "description": "One line under the name. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 300
                        }
                    },
                    "owner_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "format": "int64",
                        "description": "Set it and the page is that host's own personal meeting (`/meet/{slug}`); leave it null and it is the organisation's catalogue (`/book/{slug}`)."
                    },
                    "is_active": {
                        "type": "boolean",
                        "description": "Off takes the page out of every surface without deleting it."
                    },
                    "scheduling_type": {
                        "type": "string",
                        "enum": [
                            "personal",
                            "round_robin",
                            "collective",
                            "group"
                        ],
                        "description": "`round_robin` routes to the least-booked host; `personal` is one host; `collective` needs every host free at once; `group` shares a slot between attendees."
                    },
                    "brand_display": {
                        "type": "string",
                        "enum": [
                            "photo",
                            "logo",
                            "none"
                        ],
                        "description": "What the standalone page puts above the card. A catalogue page only takes `logo` or `none` — a face belongs to a person."
                    },
                    "duration_minutes": {
                        "type": "integer",
                        "minimum": 5,
                        "maximum": 480,
                        "description": "The default length, always offered."
                    },
                    "duration_options": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "integer",
                            "minimum": 5,
                            "maximum": 480
                        },
                        "description": "Extra lengths offered on top of the default. Null or empty means one length and no choice shown."
                    },
                    "slot_interval_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 5,
                        "maximum": 480,
                        "description": "How often a slot starts, aligned to the clock. Null steps by the duration."
                    },
                    "buffer_before_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 120,
                        "description": "Time kept clear before a booking. Buffers pad bookings; they do not thin out the grid."
                    },
                    "buffer_after_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 120,
                        "description": "Time kept clear after a booking."
                    },
                    "minimum_notice_minutes": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "How far ahead a visitor must book."
                    },
                    "daily_limit": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 1,
                        "description": "Most bookings per day. Null is unlimited."
                    },
                    "seats_per_slot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 2,
                        "description": "Only meaningful for `group`. Null is unlimited."
                    },
                    "offered_languages": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "string",
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "description": "The languages the meeting can be held in. Kept whichever `language_source` is in force, so switching back and forth loses nothing. Declaring none means every language."
                    },
                    "language_source": {
                        "type": "string",
                        "enum": [
                            "listed",
                            "hosts"
                        ],
                        "description": "`listed` offers exactly the list above; `hosts` offers the union of what the bookable pool has declared, so a language is never promised without somebody who can hold the meeting in it."
                    },
                    "location_type": {
                        "type": "string",
                        "enum": [
                            "google_meet",
                            "in_person"
                        ],
                        "description": "`in_person` replaces the video link with the address everywhere — page, calendar event, emails, `.ics`."
                    },
                    "location": {
                        "type": "object",
                        "description": "The address, when the meeting happens somewhere rather than in a video room. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 200
                        }
                    },
                    "hours_source": {
                        "type": "string",
                        "enum": [
                            "page",
                            "hosts"
                        ],
                        "description": "`page` slices the week written below; `hosts` reads each host's own week, on their own clock, falling back to the page's for a host who has not written one."
                    },
                    "weekly_hours": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/WeeklyHourWindow"
                        },
                        "description": "The week the page is open. In `hosts` mode this stays as the per-host fallback."
                    },
                    "event_hours": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/EventHourWindow"
                        },
                        "description": "Opening hours for a real event, overriding `weekly_hours` on the dates they cover. An undated row is the ordinary day. Setting any of these **requires** `timezone`."
                    },
                    "available_from": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First bookable date. Together with `available_until` this pins the page to a real event and overrides the rolling horizon."
                    },
                    "available_until": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last bookable date. Must not precede `available_from`."
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "An IANA timezone. Required once `event_hours` are set: one clock for everyone, or the same \"10:00\" lands at a different instant per host."
                    },
                    "collect_details_first": {
                        "type": "boolean",
                        "description": "Ask the questions before showing the times. Ignored on a page with event dates — those always show the times first."
                    },
                    "questions": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "$ref": "#/components/schemas/QuestionDefinition"
                        },
                        "description": "What the booking form asks on top of first name, last name and email, which are always asked."
                    },
                    "host_ids": {
                        "type": "array",
                        "items": {
                            "type": "integer",
                            "format": "int64"
                        },
                        "description": "The host pool, as user ids. Not a column — it is synced as a relation. Omit on an update to leave the pool alone; on a personal meeting it is overruled back to the owner."
                    }
                }
            },
            "NewsletterCampaignCreate": {
                "type": "object",
                "required": [
                    "subject",
                    "body_html"
                ],
                "additionalProperties": false,
                "description": "A newsletter edition. `subject` and `body_html` are locale maps; a locale with no `body_html` cannot be sent, and the fallback locale (`en`) must have one before a send is allowed.",
                "properties": {
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Unique. Generated from the English subject when omitted."
                    },
                    "from_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Overrides the sender name for this edition only."
                    },
                    "from_email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email",
                        "maxLength": 255,
                        "description": "Overrides the sender address. Keep it on the verified sending domain or DKIM/SPF/DMARC break."
                    },
                    "scheduled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Must be in the future. Setting it here does not schedule the send — `POST …/send` with `mode=schedule` does."
                    },
                    "subject": {
                        "type": "object",
                        "description": "The subject line. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 255
                        }
                    },
                    "preheader": {
                        "type": "object",
                        "description": "The preview text after the subject in an inbox. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    "body_html": {
                        "type": "object",
                        "description": "The edition body, as HTML. Tokens `{{unsubscribe_url}}`, `{{ view_as_page_url }}`, `{{ site.url }}` and `{{ subscriber.email }}` are resolved per recipient. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string"
                        }
                    }
                }
            },
            "NewsletterCampaignUpdate": {
                "type": "object",
                "required": [],
                "additionalProperties": false,
                "description": "A newsletter edition. `subject` and `body_html` are locale maps; a locale with no `body_html` cannot be sent, and the fallback locale (`en`) must have one before a send is allowed.",
                "properties": {
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Unique. Generated from the English subject when omitted."
                    },
                    "from_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Overrides the sender name for this edition only."
                    },
                    "from_email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email",
                        "maxLength": 255,
                        "description": "Overrides the sender address. Keep it on the verified sending domain or DKIM/SPF/DMARC break."
                    },
                    "scheduled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Must be in the future. Setting it here does not schedule the send — `POST …/send` with `mode=schedule` does."
                    },
                    "subject": {
                        "type": "object",
                        "description": "The subject line. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string",
                            "maxLength": 255
                        }
                    },
                    "preheader": {
                        "type": "object",
                        "description": "The preview text after the subject in an inbox. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "maxLength": 255
                        }
                    },
                    "body_html": {
                        "type": "object",
                        "description": "The edition body, as HTML. Tokens `{{unsubscribe_url}}`, `{{ view_as_page_url }}`, `{{ site.url }}` and `{{ subscriber.email }}` are resolved per recipient. Keyed by locale (en, es, pt, fr, de, it, pl); sending one locale leaves the others as they were.",
                        "minProperties": 1,
                        "propertyNames": {
                            "enum": [
                                "en",
                                "es",
                                "pt",
                                "fr",
                                "de",
                                "it",
                                "pl"
                            ]
                        },
                        "additionalProperties": {
                            "type": "string"
                        }
                    }
                }
            }
        }
    }
}