{
  "openapi": "3.1.0",
  "info": {
    "title": "Grofomo API",
    "version": "1.0.0",
    "summary": "Build an event website on live festival data.",
    "description": "Read-only feeds for an event website, plus light writes for mailing-list signups and form submissions. Every response is wrapped in `{ apiVersion, data }`, or `{ apiVersion, error }` on failure.\n\nMost endpoints require `?surface=web`: the parameter defaults to the mobile-app surface, which is gated on a different visibility setting, so omitting it can return `404` for a live event."
  },
  "servers": [
    {
      "url": "https://api.grofomo.com"
    }
  ],
  "security": [
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Event"
    },
    {
      "name": "Lineup"
    },
    {
      "name": "Content"
    },
    {
      "name": "Tickets & merch"
    },
    {
      "name": "Forms & signups"
    },
    {
      "name": "Discovery"
    }
  ],
  "paths": {
    "/v1/events/{slug}/lineup": {
      "get": {
        "operationId": "events_lineup",
        "summary": "Event lineup",
        "description": "The published lineup for an event — every set, with its stage, start and end time.\n\nSlots arrive as one flat `slots` array rather than pre-grouped, so you can group by `day`, by `stage`, or by both, depending on how your page is laid out. A back-to-back set carries its full roster in `artists`, with the lead act repeated in `artist`; an unannounced slot has `artist: null` and a `placeholderLabel` to render instead.\n\nThis reads the organiser's **published** snapshot, never their working draft. A lineup being rearranged in the console will not appear here until it is published, which is what makes it safe to render straight onto a public page.\n\nOn the web surface the response also carries `schemaOrg`: a ready-made `MusicEvent` JSON-LD block you can drop into a `<script type=\"application/ld+json\">` tag for rich search results.",
        "tags": [
          "Lineup"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "eventId": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "eventSlug": {
                          "type": "string"
                        },
                        "surface": {
                          "type": "string",
                          "enum": [
                            "app",
                            "web"
                          ],
                          "description": "Echo of the surface this response was built for."
                        },
                        "slots": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_lineup___schema0"
                          },
                          "description": "Every published set, as one flat list. Group by `day` or `stage` yourself, depending on how your page is laid out."
                        },
                        "schemaOrg": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_lineup___schema17"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "A ready-made schema.org `MusicEvent` block. Serialise it into a `<script type=\"application/ld+json\">` tag. Null on the app surface."
                        },
                        "lastUpdatedAt": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_lineup___schema18"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Latest edit across the event and every set here. Good for a \"last updated\" line."
                        }
                      },
                      "required": [
                        "eventId",
                        "eventSlug",
                        "surface",
                        "slots",
                        "schemaOrg",
                        "lastUpdatedAt"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "eventSlug": "summer-fest",
                    "surface": "web",
                    "slots": [
                      {
                        "id": "3e5a7c9b-0d2f-4e6a-9b8c-1d3f5a7e9c02",
                        "stage": "Main Stage",
                        "day": "2026-07-17",
                        "startTime": "21:30",
                        "endTime": "23:00",
                        "durationMinutes": 90,
                        "billingTier": "headliner",
                        "isPlaceholder": false,
                        "placeholderLabel": null,
                        "placeholderDescription": null,
                        "featured": true,
                        "createdAtIso": "2026-04-02T11:20:00.000Z",
                        "artist": {
                          "id": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a13",
                          "name": "Nala Vex",
                          "imageUrl": "https://cdn.grofomo.com/summer-fest/artists/nala-vex.jpg",
                          "genres": [
                            "House",
                            "Breaks"
                          ],
                          "hometown": "Manchester",
                          "region": "England",
                          "country": "GB",
                          "locationLabel": "Manchester, England, United Kingdom"
                        },
                        "artists": [
                          {
                            "id": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a13",
                            "name": "Nala Vex",
                            "imageUrl": "https://cdn.grofomo.com/summer-fest/artists/nala-vex.jpg",
                            "genres": [
                              "House",
                              "Breaks"
                            ],
                            "hometown": "Manchester",
                            "region": "England",
                            "country": "GB",
                            "locationLabel": "Manchester, England, United Kingdom"
                          }
                        ]
                      },
                      {
                        "id": "47a9e1c3-5b8d-4f20-9c6e-3a1b7d5f9e02",
                        "stage": "The Orchard",
                        "day": "2026-07-18",
                        "startTime": "23:00",
                        "endTime": "01:00",
                        "durationMinutes": 120,
                        "billingTier": "special guest",
                        "isPlaceholder": false,
                        "placeholderLabel": null,
                        "placeholderDescription": null,
                        "featured": false,
                        "createdAtIso": "2026-04-02T11:22:00.000Z",
                        "artist": {
                          "id": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b04",
                          "name": "Sonny Delrey",
                          "imageUrl": "https://cdn.grofomo.com/summer-fest/artists/sonny-delrey.jpg",
                          "genres": [
                            "Disco",
                            "Italo"
                          ],
                          "hometown": "Bristol",
                          "region": "England",
                          "country": "GB",
                          "locationLabel": "Bristol, England, United Kingdom"
                        },
                        "artists": [
                          {
                            "id": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b04",
                            "name": "Sonny Delrey",
                            "imageUrl": "https://cdn.grofomo.com/summer-fest/artists/sonny-delrey.jpg",
                            "genres": [
                              "Disco",
                              "Italo"
                            ],
                            "hometown": "Bristol",
                            "region": "England",
                            "country": "GB",
                            "locationLabel": "Bristol, England, United Kingdom"
                          },
                          {
                            "id": "5b8d2f4a-6c0e-4a13-9d7b-8f2a6c4e0d15",
                            "name": "Kiruna",
                            "imageUrl": null,
                            "genres": [
                              "Ambient"
                            ],
                            "hometown": "Gothenburg",
                            "region": null,
                            "country": "SE",
                            "locationLabel": "Gothenburg, Sweden"
                          }
                        ]
                      },
                      {
                        "id": "a3c5e7d9-2b4f-4061-8e9a-7c5d3b1f9a26",
                        "stage": "Main Stage",
                        "day": "2026-07-19",
                        "startTime": "22:00",
                        "endTime": "23:30",
                        "durationMinutes": 90,
                        "billingTier": "headliner",
                        "isPlaceholder": true,
                        "placeholderLabel": "Sunday headliner",
                        "placeholderDescription": "Announced 1 June.",
                        "featured": true,
                        "createdAtIso": "2026-04-02T11:25:00.000Z",
                        "artist": null,
                        "artists": []
                      }
                    ],
                    "schemaOrg": {
                      "@context": "https://schema.org",
                      "@type": "MusicEvent",
                      "name": "Summer Fest",
                      "startDate": "2026-07-17T12:00:00+01:00",
                      "endDate": "2026-07-19T23:59:00+01:00",
                      "location": {
                        "@type": "Place",
                        "name": "Ashton Court",
                        "address": "Ashton Court Estate, Bristol BS41 9JN, United Kingdom"
                      },
                      "performer": [
                        {
                          "@type": "MusicGroup",
                          "name": "Nala Vex"
                        },
                        {
                          "@type": "MusicGroup",
                          "name": "Sonny Delrey"
                        }
                      ],
                      "dateModified": "2026-06-01T09:15:00.000Z"
                    },
                    "lastUpdatedAt": "2026-06-01T09:15:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/artists/{artistId}": {
      "get": {
        "operationId": "events_artist",
        "summary": "Artist detail",
        "description": "An artist's full profile — biography, image and social links — together with every set they play at this event.\n\nUse it for an artist page linked from the lineup.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Lineup"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "artistId",
            "in": "path",
            "required": true,
            "description": "From the lineup response.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string",
                          "description": "Billed name."
                        },
                        "realName": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema0"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Legal name, when the artist has published one."
                        },
                        "bioShort": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema1"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "One or two sentences, for a lineup card."
                        },
                        "bioLong": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema2"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Full biography, for an artist page."
                        },
                        "imageUrl": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema3"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Press shot."
                        },
                        "genres": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_artist___schema4"
                          },
                          "description": "Free-text genre tags. May be empty."
                        },
                        "website": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema5"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "The artist's own site."
                        },
                        "socials": {
                          "type": "object",
                          "propertyNames": {
                            "$ref": "#/components/schemas/events_artist___schema6"
                          },
                          "additionalProperties": {
                            "$ref": "#/components/schemas/events_artist___schema7"
                          },
                          "description": "Profile URLs keyed by platform slug (`instagram`, `spotify`, `soundcloud`, …). Blank entries are stripped server-side, so every value here is a real URL."
                        },
                        "hometown": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema8"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "City."
                        },
                        "region": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema9"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "State or province."
                        },
                        "country": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema10"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Raw ISO 3166-1 alpha-2 code, e.g. \"GB\"."
                        },
                        "locationLabel": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_artist___schema11"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Pre-formatted location for display. Render this rather than joining the parts."
                        },
                        "sets": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_artist___schema12"
                          },
                          "description": "Every set this artist plays at this event."
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "realName",
                        "bioShort",
                        "bioLong",
                        "imageUrl",
                        "genres",
                        "website",
                        "socials",
                        "hometown",
                        "region",
                        "country",
                        "locationLabel",
                        "sets"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "id": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a13",
                    "name": "Nala Vex",
                    "realName": null,
                    "bioShort": "Manchester house producer with a decade of dancefloor edits behind her.",
                    "bioLong": "Nala Vex has spent ten years turning warehouse rooms inside out, first through the Low Ceiling parties and later on her own Vexed imprint. Her sets move between rolling house, UK breaks and whatever record she found on Thursday.",
                    "imageUrl": "https://cdn.grofomo.com/summer-fest/artists/nala-vex.jpg",
                    "genres": [
                      "House",
                      "Breaks"
                    ],
                    "website": "https://nalavex.example",
                    "socials": {
                      "instagram": "https://instagram.com/nalavex",
                      "soundcloud": "https://soundcloud.com/nalavex"
                    },
                    "hometown": "Manchester",
                    "region": "England",
                    "country": "GB",
                    "locationLabel": "Manchester, England, United Kingdom",
                    "sets": [
                      {
                        "lineupId": "3e5a7c9b-0d2f-4e6a-9b8c-1d3f5a7e9c02",
                        "stage": "Main Stage",
                        "day": "2026-07-17",
                        "startTime": "21:30",
                        "endTime": "23:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/faq": {
      "get": {
        "operationId": "events_faq",
        "summary": "Event FAQs",
        "description": "The event's FAQs, in the organiser's own categories and order.\n\nAnswers arrive as **pre-rendered, sanitised HTML** in `answerHtml`, so you can inject them directly without running a rich-text renderer of your own.\n\nOn the web surface the response carries an `FAQPage` JSON-LD block.",
        "tags": [
          "Content"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "eventId": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "surface": {
                          "type": "string",
                          "enum": [
                            "app",
                            "web"
                          ],
                          "description": "Echo of the surface this response was built for."
                        },
                        "categories": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_faq___schema0"
                          },
                          "description": "Categories, in the organiser's order."
                        },
                        "schemaOrg": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_faq___schema4"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "A ready-made schema.org `FAQPage` block for a JSON-LD script tag. Null on the app surface."
                        },
                        "lastUpdatedAt": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_faq___schema5"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Most recent edit across every entry. Null when there are none."
                        },
                        "generatedAt": {
                          "type": "string",
                          "description": "When this response was built."
                        }
                      },
                      "required": [
                        "eventId",
                        "surface",
                        "categories",
                        "schemaOrg",
                        "lastUpdatedAt",
                        "generatedAt"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "surface": "web",
                    "categories": [
                      {
                        "id": "getting-there",
                        "name": "Getting there",
                        "faqs": [
                          {
                            "id": "72e4c6a8-3d5f-4b90-9a1c-6e8d0f2b4a37",
                            "question": "Is there parking on site?",
                            "answerDoc": {
                              "type": "doc",
                              "content": [
                                {
                                  "type": "paragraph",
                                  "content": [
                                    {
                                      "type": "text",
                                      "text": "Yes, but it must be booked in advance through the "
                                    },
                                    {
                                      "type": "text",
                                      "text": "ticket store",
                                      "marks": [
                                        {
                                          "type": "link",
                                          "attrs": {
                                            "href": "https://grfm.to/e/summer-fest"
                                          }
                                        }
                                      ]
                                    },
                                    {
                                      "type": "text",
                                      "text": ". Gates open at 10:00 each day."
                                    }
                                  ]
                                }
                              ]
                            },
                            "answerPlain": "Yes, but it must be booked in advance through the ticket store. Gates open at 10:00 each day.",
                            "answerHtml": "<p>Yes, but it must be booked in advance through the <a href=\"https://grfm.to/e/summer-fest\">ticket store</a>. Gates open at 10:00 each day.</p>",
                            "updatedAt": "2026-05-14T16:02:00.000Z"
                          }
                        ]
                      },
                      {
                        "id": null,
                        "name": "On the day",
                        "faqs": [
                          {
                            "id": "e8a0c2d4-7f9b-4d51-8c3e-9b7f5d1a3c48",
                            "question": "Can I bring my own drinks?",
                            "answerDoc": {
                              "type": "doc",
                              "content": [
                                {
                                  "type": "paragraph",
                                  "content": [
                                    {
                                      "type": "text",
                                      "text": "Sealed water only, up to 500ml. Everything else is available on site."
                                    }
                                  ]
                                }
                              ]
                            },
                            "answerPlain": "Sealed water only, up to 500ml. Everything else is available on site.",
                            "answerHtml": "<p>Sealed water only, up to 500ml. Everything else is available on site.</p>",
                            "updatedAt": "2026-05-14T16:04:00.000Z"
                          }
                        ]
                      }
                    ],
                    "schemaOrg": {
                      "@context": "https://schema.org",
                      "@type": "FAQPage",
                      "mainEntity": [
                        {
                          "@type": "Question",
                          "name": "Is there parking on site?",
                          "acceptedAnswer": {
                            "@type": "Answer",
                            "text": "Yes, but it must be booked in advance through the ticket store."
                          }
                        }
                      ]
                    },
                    "lastUpdatedAt": "2026-05-14T16:04:00.000Z",
                    "generatedAt": "2026-06-01T09:15:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/news": {
      "get": {
        "operationId": "events_news",
        "summary": "News index",
        "description": "The event's published news articles, newest first. Each entry is a summary — fetch a single article for its body.\n\nDrafts and articles scheduled for later never appear.",
        "tags": [
          "Content"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "eventId": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "articles": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_news___schema0"
                          },
                          "description": "Published articles, newest first. Drafts and scheduled posts never appear."
                        },
                        "lastUpdatedAt": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_news___schema2"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Most recent change across these articles. Null when there are none."
                        },
                        "generatedAt": {
                          "type": "string",
                          "description": "When this response was built. Useful for a cache debug line."
                        }
                      },
                      "required": [
                        "eventId",
                        "articles",
                        "lastUpdatedAt",
                        "generatedAt"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "articles": [
                      {
                        "slug": "second-wave-announced",
                        "title": "Second wave announced",
                        "excerpt": "Twelve more names join the bill, including a Sunday close on the Main Stage.",
                        "heroImageUrl": "https://cdn.grofomo.com/summer-fest/news/second-wave.jpg",
                        "tags": [
                          "lineup"
                        ],
                        "author": "Riot Promotions",
                        "publishedAt": "2026-05-28T09:00:00.000Z",
                        "updatedAt": "2026-05-28T09:00:00.000Z"
                      },
                      {
                        "slug": "travel-and-parking",
                        "title": "Travel and parking, explained",
                        "excerpt": "Shuttle times from Temple Meads, and how the pre-book car park works.",
                        "heroImageUrl": null,
                        "tags": [
                          "travel",
                          "practical"
                        ],
                        "author": null,
                        "publishedAt": "2026-05-12T08:30:00.000Z",
                        "updatedAt": "2026-05-19T14:10:00.000Z"
                      }
                    ],
                    "lastUpdatedAt": "2026-05-28T09:00:00.000Z",
                    "generatedAt": "2026-06-01T09:15:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/news/{articleSlug}": {
      "get": {
        "operationId": "events_news-article",
        "summary": "News article",
        "description": "One published article, including its body as sanitised HTML.\n\nOn the web surface the response carries a `NewsArticle` JSON-LD block.",
        "tags": [
          "Content"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "articleSlug",
            "in": "path",
            "required": true,
            "description": "The article's slug, from the news index.",
            "schema": {
              "type": "string"
            },
            "example": "first-wave-announced"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "slug": {
                          "type": "string",
                          "description": "Pass this to the single-article endpoint for the body."
                        },
                        "title": {
                          "type": "string"
                        },
                        "excerpt": {
                          "type": "string",
                          "description": "Short standfirst, for a listing card."
                        },
                        "heroImageUrl": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_news_article___schema0"
                          },
                          "description": "Free-text tags, e.g. \"lineup\". May be empty."
                        },
                        "author": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "publishedAt": {
                          "type": "string",
                          "description": "Publication time. The index is sorted newest first on this."
                        },
                        "updatedAt": {
                          "type": "string"
                        },
                        "bodyMarkdown": {
                          "type": "string",
                          "description": "The canonical body, in markdown."
                        },
                        "bodyHtml": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_news_article___schema1"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "The same body pre-rendered to sanitised HTML, so you need no markdown renderer."
                        },
                        "seoTitle": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_news_article___schema2"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Override for the page `<title>`, when the organiser set one."
                        },
                        "seoDescription": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_news_article___schema3"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Override for the meta description."
                        },
                        "schemaOrg": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_news_article___schema4"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "A ready-made schema.org `NewsArticle` block for a JSON-LD script tag."
                        }
                      },
                      "required": [
                        "slug",
                        "title",
                        "excerpt",
                        "heroImageUrl",
                        "tags",
                        "author",
                        "publishedAt",
                        "updatedAt",
                        "bodyMarkdown",
                        "bodyHtml",
                        "seoTitle",
                        "seoDescription",
                        "schemaOrg"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "slug": "second-wave-announced",
                    "title": "Second wave announced",
                    "excerpt": "Twelve more names join the bill, including a Sunday close on the Main Stage.",
                    "heroImageUrl": "https://cdn.grofomo.com/summer-fest/news/second-wave.jpg",
                    "tags": [
                      "lineup"
                    ],
                    "author": "Riot Promotions",
                    "publishedAt": "2026-05-28T09:00:00.000Z",
                    "updatedAt": "2026-05-28T09:00:00.000Z",
                    "bodyMarkdown": "## Twelve more names\n\nThe second wave lands today, and it fills out both the Friday night Orchard programme and Sunday on the Main Stage.\n\nTickets for all three days are on sale now.",
                    "bodyHtml": "<h2>Twelve more names</h2>\n<p>The second wave lands today, and it fills out both the Friday night Orchard programme and Sunday on the Main Stage.</p>\n<p>Tickets for all three days are on sale now.</p>",
                    "seoTitle": "Summer Fest 2026: second wave announced",
                    "seoDescription": "Twelve more names join the Summer Fest bill, including a Sunday Main Stage close.",
                    "schemaOrg": {
                      "@context": "https://schema.org",
                      "@type": "NewsArticle",
                      "headline": "Second wave announced",
                      "datePublished": "2026-05-28T09:00:00.000Z",
                      "dateModified": "2026-05-28T09:00:00.000Z",
                      "author": {
                        "@type": "Organization",
                        "name": "Riot Promotions"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/broadcasts": {
      "get": {
        "operationId": "events_broadcasts",
        "summary": "Announcements",
        "description": "Announcements the organiser has pushed to attendees — stage changes, weather notices, running-order updates.\n\nGood for a live \"latest updates\" strip during the event itself. Never cached, so it is safe to poll.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Content"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "broadcasts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "title": {
                                "type": "string"
                              },
                              "body": {
                                "type": "string"
                              },
                              "category": {
                                "type": "string",
                                "enum": [
                                  "reminder",
                                  "update",
                                  "marketing"
                                ],
                                "description": "What kind of announcement this was. Mirrors the push opt-in it was sent under."
                              },
                              "sentAt": {
                                "type": "string",
                                "description": "When it went out. Newest first."
                              },
                              "deepLink": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/events_broadcasts___schema0"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "Where the announcement pointed, when it pointed anywhere."
                              }
                            },
                            "required": [
                              "id",
                              "title",
                              "body",
                              "category",
                              "sentAt",
                              "deepLink"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "broadcasts"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "broadcasts": [
                      {
                        "id": "0d2f4a6c-8e0b-4c73-9f5a-2c4e6a8d0b59",
                        "title": "Main Stage running 20 minutes late",
                        "body": "Weather delay this morning. Everything from 18:00 shifts back by 20 minutes.",
                        "category": "update",
                        "sentAt": "2026-07-18T16:42:00.000Z",
                        "deepLink": "https://grfm.to/e/summer-fest"
                      },
                      {
                        "id": "4a6c8e0d-2f4b-4e95-8a7c-1f3d5b7e9c60",
                        "title": "Last entry is 21:00",
                        "body": "Gates close for good at 21:00 tonight. Plan your arrival.",
                        "category": "reminder",
                        "sentAt": "2026-07-17T14:00:00.000Z",
                        "deepLink": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/tickets": {
      "get": {
        "operationId": "events_tickets",
        "summary": "Ticket availability",
        "description": "The ticket types and release tiers a visitor is allowed to see, with live availability.\n\nVisibility is resolved **server-side**, so this only ever returns what the public should see: time-gated tiers appear when they open, and hidden sold-out tiers stay hidden.\n\nRead `soldOut` rather than inferring from `available`. `available` is `null` both when a tier is unlimited **and** when the organiser has chosen not to publish counts — `soldOut` is always authoritative.\n\nSelling tickets from your own site is not part of this API yet. Use this feed to show prices and availability, and link to the Grofomo ticket page to sell; an embeddable checkout is coming.",
        "tags": [
          "Tickets & merch"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          },
          {
            "name": "promo",
            "in": "query",
            "required": false,
            "description": "A live unlock code, to reveal promo-gated ticket types.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "categories": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_tickets___schema0"
                          },
                          "description": "Optional grouping for the types below."
                        },
                        "types": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_tickets___schema1"
                          },
                          "description": "Only what this caller is allowed to see. Hidden and promo-gated types are filtered server-side."
                        },
                        "quantityDiscounts": {
                          "description": "Automatic discounts, in enough detail to advertise (\"Buy 4+ and save 10%\") and preview locally. The server stays authoritative at checkout.",
                          "$ref": "#/components/schemas/events_tickets___schema16"
                        }
                      },
                      "required": [
                        "categories",
                        "types"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "categories": [
                      {
                        "id": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c35",
                        "title": "Weekend",
                        "description": "All three days, with camping."
                      }
                    ],
                    "types": [
                      {
                        "id": "b2d4a6f8-1c3e-4a5b-8d7f-2e9c0a1b3d46",
                        "categoryId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c35",
                        "title": "Weekend camping ticket",
                        "description": "Entry Friday to Sunday, plus a camping pitch from Friday 10:00.",
                        "descriptionDoc": {
                          "type": "doc",
                          "content": [
                            {
                              "type": "paragraph",
                              "content": [
                                {
                                  "type": "text",
                                  "text": "Entry Friday to Sunday, plus a camping pitch from Friday 10:00."
                                }
                              ]
                            }
                          ]
                        },
                        "ticketsPerGroup": 1,
                        "minPerOrder": 1,
                        "maxPerOrder": 6,
                        "isHighlighted": true,
                        "highlightMessage": "Most popular",
                        "paymentPlan": {
                          "depositBps": 2500,
                          "installments": 3
                        },
                        "releases": [
                          {
                            "id": "3e5a7c9b-0d2f-4e6a-9b8c-1d3f5a7e9c03",
                            "label": "Tier 1",
                            "pricePennies": 9500,
                            "currency": "GBP",
                            "available": null,
                            "capacity": null,
                            "closesAt": null,
                            "opensAt": null,
                            "upcoming": false,
                            "soldOut": true,
                            "closed": false
                          },
                          {
                            "id": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a14",
                            "label": "Tier 2",
                            "pricePennies": 11000,
                            "currency": "GBP",
                            "available": 340,
                            "capacity": 1500,
                            "closesAt": "2026-07-10T22:59:00.000Z",
                            "opensAt": null,
                            "upcoming": false,
                            "soldOut": false,
                            "closed": false
                          },
                          {
                            "id": "47a9e1c3-5b8d-4f20-9c6e-3a1b7d5f9e03",
                            "label": "Final release",
                            "pricePennies": 12500,
                            "currency": "GBP",
                            "available": null,
                            "capacity": null,
                            "closesAt": null,
                            "opensAt": "2026-07-11T09:00:00.000Z",
                            "upcoming": true,
                            "soldOut": false,
                            "closed": false
                          }
                        ]
                      }
                    ],
                    "quantityDiscounts": [
                      {
                        "id": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b05",
                        "name": "Group of four",
                        "kind": "tiered_percent",
                        "applicableReleaseIds": [
                          "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a14"
                        ],
                        "tiers": [
                          {
                            "minQuantity": 4,
                            "value": 1000
                          }
                        ],
                        "combinableWithPromo": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/merch": {
      "get": {
        "operationId": "events_merch",
        "summary": "Merch catalogue",
        "description": "The event's merch catalogue: each product with its colours, sizes, print zones and price.\n\nProducts carry the plain garment image plus the event logo to overlay, so you can render a preview without anyone shipping you mock-ups.",
        "tags": [
          "Tickets & merch"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "eventSlug": {
                          "type": "string"
                        },
                        "logoUrl": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_merch___schema0"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "The logo to overlay on each print zone — so you can render previews without any mock-ups."
                        },
                        "deliveryPennies": {
                          "default": 0,
                          "description": "Flat delivery fee in minor units, for shipped orders. 0 means free, or collection only.",
                          "$ref": "#/components/schemas/events_merch___schema1"
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "value": {
                                "type": "string",
                                "description": "Product id. Matches the option value on a `merch_items` form field."
                              },
                              "name": {
                                "type": "string"
                              },
                              "garmentType": {
                                "type": "string",
                                "description": "What the garment is, e.g. \"Heavyweight cotton t-shirt\"."
                              },
                              "description": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "pricePennies": {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991,
                                "description": "Unit price in minor units, so 2500 is £25.00."
                              },
                              "currency": {
                                "type": "string",
                                "description": "ISO 4217 code, e.g. \"GBP\"."
                              },
                              "sizes": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/events_merch___schema2"
                                },
                                "description": "Available sizes, in display order."
                              },
                              "colours": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/events_merch___schema3"
                                },
                                "description": "Colourways. Each carries the PLAIN garment image; overlay the event logo yourself."
                              },
                              "printZones": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/events_merch___schema9"
                                },
                                "description": "Where to place the logo on each side. All coordinates are 0–1 fractions, so a preview scales freely."
                              },
                              "outline": {
                                "description": "Traced garment silhouette (0–1 points) driving the `clip` mask. Absent means use a default tee shape.",
                                "$ref": "#/components/schemas/events_merch___schema13"
                              }
                            },
                            "required": [
                              "value",
                              "name",
                              "garmentType",
                              "description",
                              "pricePennies",
                              "currency",
                              "sizes",
                              "colours",
                              "printZones"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "eventSlug",
                        "logoUrl",
                        "deliveryPennies",
                        "items"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "eventSlug": "summer-fest",
                    "logoUrl": "https://cdn.grofomo.com/summer-fest/brand/logo.svg",
                    "deliveryPennies": 495,
                    "items": [
                      {
                        "value": "8e0d2f4a-6c8b-4f17-9e2a-3b5d7f9c1e71",
                        "name": "Summer Fest 2026 Tee",
                        "garmentType": "Heavyweight cotton t-shirt",
                        "description": "Organic cotton, boxy fit, printed in Bristol.",
                        "pricePennies": 2500,
                        "currency": "GBP",
                        "sizes": [
                          "S",
                          "M",
                          "L",
                          "XL",
                          "XXL"
                        ],
                        "colours": [
                          {
                            "label": "Bone",
                            "hex": "#FAFAF7",
                            "frontImageUrl": "https://cdn.grofomo.com/summer-fest/merch/tee-bone-front.png",
                            "backImageUrl": "https://cdn.grofomo.com/summer-fest/merch/tee-bone-back.png",
                            "logoColor": "black",
                            "outOfStock": false
                          },
                          {
                            "label": "Carbon",
                            "hex": "#080808",
                            "frontImageUrl": "https://cdn.grofomo.com/summer-fest/merch/tee-carbon-front.png",
                            "backImageUrl": "https://cdn.grofomo.com/summer-fest/merch/tee-carbon-back.png",
                            "logoColor": "white",
                            "outOfStock": true
                          }
                        ],
                        "printZones": [
                          {
                            "side": "front",
                            "x": 0.5,
                            "y": 0.34,
                            "scale": 0.32,
                            "recolor": true,
                            "logoColor": "auto",
                            "clip": true
                          },
                          {
                            "side": "back",
                            "x": 0.5,
                            "y": 0.28,
                            "scale": 0.55,
                            "recolor": true,
                            "logoColor": "auto",
                            "clip": true
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/subscribe": {
      "post": {
        "operationId": "events_subscribe",
        "summary": "Mailing-list signup",
        "description": "Post an email address straight into the organiser's mailing list, so your site can host its own signup block rather than embedding a hosted form.\n\n**Consent is the organiser's to collect.** Only submit an address when the visitor has actively asked to hear from them — this endpoint records a signup, it does not make one lawful.\n\nRequires a Cloudflare Turnstile token, so it is not usable from a script.",
        "tags": [
          "Forms & signups"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "ok": true,
                    "submissionId": "a7b8c9d0-e1f2-4345-8678-9abcdef01234"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`bad_request` — The email address was not valid.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "bad_request",
                    "message": "The email address was not valid."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "email": "fan@example.com",
                "name": "Alex Doe",
                "turnstileToken": "<token from the Turnstile widget>"
              }
            }
          }
        }
      }
    },
    "/v1/forms/{key}": {
      "get": {
        "operationId": "forms_definition",
        "summary": "Form definition",
        "description": "A published form's definition — its fields, types, labels and validation — so you can render it in your own design instead of embedding a hosted page.\n\nArtist-picker fields arrive with their options already filled in from the event's lineup.",
        "tags": [
          "Forms & signups"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The form's key, from the organiser's console.",
            "schema": {
              "type": "string"
            },
            "example": "volunteer-signup"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "description": "The form key. Post submissions back to this same key."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "feedback",
                            "mailing_list",
                            "contact",
                            "merch_enquiry"
                          ],
                          "description": "What the form is for."
                        },
                        "eventId": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/forms_definition___schema0"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Null for an organisation-wide form not tied to one event."
                        },
                        "eventSlug": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "title": {
                          "type": "string"
                        },
                        "version": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991,
                          "description": "Definition version. Send it back with the submission so the answers can be audited against it."
                        },
                        "surface": {
                          "type": "string",
                          "enum": [
                            "app",
                            "web"
                          ]
                        },
                        "fields": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/forms_definition___schema1"
                          },
                          "description": "The questions, in the organiser's order."
                        },
                        "merchCatalog": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "value": {
                                "type": "string",
                                "description": "Product id. Matches the option value on a `merch_items` form field."
                              },
                              "name": {
                                "type": "string"
                              },
                              "garmentType": {
                                "type": "string",
                                "description": "What the garment is, e.g. \"Heavyweight cotton t-shirt\"."
                              },
                              "description": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "pricePennies": {
                                "type": "integer",
                                "minimum": -9007199254740991,
                                "maximum": 9007199254740991,
                                "description": "Unit price in minor units, so 2500 is £25.00."
                              },
                              "currency": {
                                "type": "string",
                                "description": "ISO 4217 code, e.g. \"GBP\"."
                              },
                              "sizes": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/forms_definition___schema9"
                                },
                                "description": "Available sizes, in display order."
                              },
                              "colours": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/forms_definition___schema10"
                                },
                                "description": "Colourways. Each carries the PLAIN garment image; overlay the event logo yourself."
                              },
                              "printZones": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/forms_definition___schema16"
                                },
                                "description": "Where to place the logo on each side. All coordinates are 0–1 fractions, so a preview scales freely."
                              },
                              "outline": {
                                "description": "Traced garment silhouette (0–1 points) driving the `clip` mask. Absent means use a default tee shape.",
                                "$ref": "#/components/schemas/forms_definition___schema20"
                              }
                            },
                            "required": [
                              "value",
                              "name",
                              "garmentType",
                              "description",
                              "pricePennies",
                              "currency",
                              "sizes",
                              "colours",
                              "printZones"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "key",
                        "kind",
                        "eventId",
                        "eventSlug",
                        "title",
                        "version",
                        "surface",
                        "fields"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "key": "volunteer-signup",
                    "kind": "contact",
                    "eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "eventSlug": "summer-fest",
                    "title": "Volunteer with us",
                    "version": 3,
                    "surface": "web",
                    "fields": [
                      {
                        "id": "full_name",
                        "type": "text",
                        "label": "Your name",
                        "required": true
                      },
                      {
                        "id": "email",
                        "type": "email",
                        "label": "Email address",
                        "required": true,
                        "role": "email"
                      },
                      {
                        "id": "shifts",
                        "type": "checkbox",
                        "label": "Which shifts can you cover?",
                        "help": "Pick as many as you like.",
                        "required": true,
                        "options": [
                          {
                            "value": "friday-evening",
                            "label": "Friday evening"
                          },
                          {
                            "value": "saturday-morning",
                            "label": "Saturday morning"
                          },
                          {
                            "value": "sunday-close",
                            "label": "Sunday close"
                          }
                        ]
                      },
                      {
                        "id": "favourite_act",
                        "type": "artist_multiselect",
                        "label": "Anyone you would love to work near?",
                        "required": false,
                        "source": "lineup",
                        "options": [
                          {
                            "value": "9c7e5a3b-1f0d-4b2e-8a6c-4d2f0b8e6a13",
                            "label": "Nala Vex"
                          },
                          {
                            "value": "d1f3b5a7-9c2e-4d80-8b6f-5a3c1e7d9b04",
                            "label": "Sonny Delrey"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/forms/{key}/submissions": {
      "post": {
        "operationId": "forms_submit",
        "summary": "Submit a form",
        "description": "Submit answers for a published form. Answers are validated against the form's own definition, so fetch the definition first and post back matching field ids.\n\nRequires a Turnstile token.",
        "tags": [
          "Forms & signups"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The form's key.",
            "schema": {
              "type": "string"
            },
            "example": "volunteer-signup"
          },
          {
            "name": "surface",
            "in": "query",
            "required": true,
            "description": "Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404.",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "app"
              ]
            },
            "example": "web"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "const": true
                        }
                      },
                      "required": [
                        "ok"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "ok": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "`bad_request` — An answer did not match the form definition.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "bad_request",
                    "message": "An answer did not match the form definition."
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "unauthorized",
                    "message": "No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "origin_not_allowed",
                    "message": "Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected."
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "answers": {
                  "full_name": "Alex Doe",
                  "email": "alex@example.com",
                  "shifts": [
                    "friday-evening",
                    "saturday-morning"
                  ]
                },
                "turnstileToken": "<token from the Turnstile widget>"
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}": {
      "get": {
        "operationId": "events_detail",
        "summary": "Event detail",
        "description": "Everything that describes the event itself — name, dates, timezone, venue, stages and the organiser's brand settings.\n\nUsually the first call a site makes.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Event"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "slug": {
                          "type": "string",
                          "description": "The URL slug, fixed once the event is created."
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_detail___schema0"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Long-form event copy."
                        },
                        "summary": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_detail___schema1"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "One line, for a card or a meta description."
                        },
                        "ticketUrl": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_detail___schema2"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Where to send someone to buy. Null when no tickets are on sale."
                        },
                        "capacity": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "familyFriendly": {
                          "anyOf": [
                            {
                              "type": "boolean"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "musicGenres": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_detail___schema3"
                          },
                          "description": "Free-text genre tags for the event as a whole."
                        },
                        "timezone": {
                          "type": "string",
                          "description": "IANA zone, e.g. \"Europe/London\". Every local time in the API is in it."
                        },
                        "dates": {
                          "type": "object",
                          "properties": {
                            "startDate": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema4"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "First day, as `YYYY-MM-DD`."
                            },
                            "endDate": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema5"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Last day, as `YYYY-MM-DD`. Equal to `startDate` for a one-day event."
                            },
                            "doorsOpen": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema6"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Local opening time as `HH:MM`."
                            },
                            "doorsClose": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema7"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Local closing time as `HH:MM`. May be after midnight."
                            },
                            "timezone": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema8"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "IANA zone, e.g. \"Europe/London\". Every `HH:MM` here is local to it."
                            },
                            "singleDay": {
                              "anyOf": [
                                {
                                  "type": "boolean"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          },
                          "required": [
                            "startDate",
                            "endDate",
                            "doorsOpen",
                            "doorsClose",
                            "timezone",
                            "singleDay"
                          ],
                          "additionalProperties": false
                        },
                        "venue": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "address": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema9"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Street address, without city or postcode."
                            },
                            "city": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "region": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema10"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "State, province or county."
                            },
                            "postcode": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "country": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema11"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Raw ISO 3166-1 alpha-2 code, e.g. \"GB\"."
                            },
                            "indoorOutdoor": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema12"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Free text, e.g. \"outdoor\"."
                            }
                          },
                          "required": [
                            "name",
                            "address",
                            "city",
                            "region",
                            "postcode",
                            "country",
                            "indoorOutdoor"
                          ],
                          "additionalProperties": false
                        },
                        "stages": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/events_detail___schema13"
                          },
                          "description": "Every stage. A lineup slot names its stage by `name`, not by `id`."
                        },
                        "brand": {
                          "type": "object",
                          "properties": {
                            "brandColors": {
                              "$ref": "#/components/schemas/events_detail___schema15"
                            },
                            "logoUrl": {
                              "$ref": "#/components/schemas/events_detail___schema17"
                            },
                            "logoVectorUrl": {
                              "$ref": "#/components/schemas/events_detail___schema19"
                            },
                            "artworkUrl": {
                              "$ref": "#/components/schemas/events_detail___schema21"
                            },
                            "coverArtUrl": {
                              "$ref": "#/components/schemas/events_detail___schema23"
                            },
                            "backgroundColor": {
                              "$ref": "#/components/schemas/events_detail___schema25"
                            },
                            "textColor": {
                              "$ref": "#/components/schemas/events_detail___schema27"
                            },
                            "accentColor": {
                              "$ref": "#/components/schemas/events_detail___schema29"
                            },
                            "fontFamily": {
                              "$ref": "#/components/schemas/events_detail___schema31"
                            },
                            "socialLinks": {
                              "$ref": "#/components/schemas/events_detail___schema33"
                            },
                            "mapImageUrl": {
                              "$ref": "#/components/schemas/events_detail___schema36"
                            },
                            "themeMode": {
                              "$ref": "#/components/schemas/events_detail___schema38"
                            },
                            "fonts": {
                              "$ref": "#/components/schemas/events_detail___schema39"
                            }
                          },
                          "required": [
                            "brandColors",
                            "logoUrl",
                            "logoVectorUrl",
                            "artworkUrl",
                            "coverArtUrl",
                            "backgroundColor",
                            "textColor",
                            "accentColor",
                            "fontFamily",
                            "socialLinks",
                            "mapImageUrl",
                            "themeMode",
                            "fonts"
                          ],
                          "additionalProperties": false,
                          "description": "The organiser's colours, logos and fonts, so your site can match the event's identity."
                        },
                        "community": {
                          "type": "object",
                          "properties": {
                            "chatUrl": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema41"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Invite link to the attendee group chat, when the organiser runs one."
                            },
                            "chatPlatform": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema42"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Which platform the chat is on, so you can show the right icon."
                            },
                            "chatLabel": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/events_detail___schema43"
                                },
                                {
                                  "type": "null"
                                }
                              ],
                              "description": "Call-to-action text, e.g. \"Summer Fest group chat\"."
                            }
                          },
                          "required": [
                            "chatUrl",
                            "chatPlatform",
                            "chatLabel"
                          ],
                          "additionalProperties": false
                        },
                        "organiser": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/events_detail___schema44"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Present only when the organiser has opted into a public profile — i.e. only when there is a page to link to."
                        }
                      },
                      "required": [
                        "id",
                        "slug",
                        "name",
                        "description",
                        "summary",
                        "ticketUrl",
                        "capacity",
                        "familyFriendly",
                        "musicGenres",
                        "timezone",
                        "dates",
                        "venue",
                        "stages",
                        "brand",
                        "community",
                        "organiser"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "id": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "slug": "summer-fest",
                    "name": "Summer Fest",
                    "description": "Three days of house, disco and breaks across two stages on the edge of Bristol, with camping from Friday morning.",
                    "summary": "Three days, two stages, Ashton Court.",
                    "ticketUrl": "https://grfm.to/e/summer-fest",
                    "capacity": 8000,
                    "familyFriendly": true,
                    "musicGenres": [
                      "House",
                      "Disco",
                      "Breaks"
                    ],
                    "timezone": "Europe/London",
                    "dates": {
                      "startDate": "2026-07-17",
                      "endDate": "2026-07-19",
                      "doorsOpen": "12:00",
                      "doorsClose": "02:00",
                      "timezone": "Europe/London",
                      "singleDay": false
                    },
                    "venue": {
                      "name": "Ashton Court",
                      "address": "Ashton Court Estate",
                      "city": "Bristol",
                      "region": "England",
                      "postcode": "BS41 9JN",
                      "country": "GB",
                      "indoorOutdoor": "outdoor"
                    },
                    "stages": [
                      {
                        "id": "main-stage",
                        "name": "Main Stage",
                        "type": "outdoor",
                        "description": "The big one, in the meadow.",
                        "imageUrl": "https://cdn.grofomo.com/summer-fest/stages/main.jpg"
                      },
                      {
                        "id": "the-orchard",
                        "name": "The Orchard",
                        "type": "tent",
                        "description": null,
                        "imageUrl": null
                      }
                    ],
                    "brand": {
                      "brandColors": [
                        "#FFEA00",
                        "#080808"
                      ],
                      "logoUrl": "https://cdn.grofomo.com/summer-fest/brand/logo.png",
                      "logoVectorUrl": "https://cdn.grofomo.com/summer-fest/brand/logo.svg",
                      "artworkUrl": "https://cdn.grofomo.com/summer-fest/brand/poster.jpg",
                      "coverArtUrl": "https://cdn.grofomo.com/summer-fest/brand/cover.jpg",
                      "backgroundColor": "#080808",
                      "textColor": "#FAFAF7",
                      "accentColor": "#FFEA00",
                      "fontFamily": "Syne",
                      "socialLinks": {
                        "instagram": "https://instagram.com/summerfest",
                        "spotify": "https://open.spotify.com/playlist/summerfest"
                      },
                      "mapImageUrl": "https://cdn.grofomo.com/summer-fest/brand/site-map.png",
                      "themeMode": "brand",
                      "fonts": {
                        "headline": {
                          "family": "Syne",
                          "url": "https://fonts.gstatic.com/s/syne/v22/8vIS7w4qzmVxsWxjBZRjr0FKM_04uT6kR47NCV5Z.ttf",
                          "weight": 700
                        },
                        "body": null
                      }
                    },
                    "community": {
                      "chatUrl": "https://chat.whatsapp.com/summerfest",
                      "chatPlatform": "whatsapp",
                      "chatLabel": "Summer Fest group chat"
                    },
                    "organiser": {
                      "id": "b2d4a6f8-1c3e-4a5b-8d7f-2e9c0a1b3d45",
                      "name": "Riot Promotions",
                      "slug": "riot-promotions",
                      "logoUrl": "https://cdn.grofomo.com/riot-promotions/logo.png"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{slug}/version": {
      "get": {
        "operationId": "events_version",
        "summary": "Content version",
        "description": "A single timestamp that moves whenever the event or its lineup is edited.\n\nPoll this instead of re-fetching the lineup. It is small and cached for 15 seconds, so you can check often and only pull the heavy feeds when the version actually changes.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Event"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's URL slug. Globally unique, and fixed once the event is created.",
            "schema": {
              "type": "string"
            },
            "example": "summer-fest"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "eventId": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "eventSlug": {
                          "type": "string"
                        },
                        "scheduleVersion": {
                          "type": "string",
                          "description": "Opaque token that changes whenever the event or its lineup is edited. Compare it with the last one you saw; only pull the heavy feeds when it differs."
                        },
                        "updatedAt": {
                          "type": "string",
                          "description": "When that last edit happened."
                        }
                      },
                      "required": [
                        "eventId",
                        "eventSlug",
                        "scheduleVersion",
                        "updatedAt"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "eventId": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                    "eventSlug": "summer-fest",
                    "scheduleVersion": "2026-06-01T09:15:00.000Z",
                    "updatedAt": "2026-06-01T09:15:00.000Z"
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No event with that slug is published on this surface. Check the slug, and check you sent `?surface=web`."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/organisers/{slug}": {
      "get": {
        "operationId": "organisers_detail",
        "summary": "Organiser profile",
        "description": "An organiser's public profile and their upcoming public events.\n\nOnly returned when the organiser has opted in to a public profile; otherwise this is a 404.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Event"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The organiser's slug.",
            "schema": {
              "type": "string"
            },
            "example": "riot-promotions"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                        },
                        "name": {
                          "type": "string"
                        },
                        "slug": {
                          "type": "string"
                        },
                        "bio": {
                          "anyOf": [
                            {
                              "$ref": "#/components/schemas/organisers_detail___schema0"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Public \"about us\" copy."
                        },
                        "logoUrl": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "logoVectorUrl": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "backgroundColor": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "textColor": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "website": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "socials": {
                          "type": "object",
                          "propertyNames": {
                            "$ref": "#/components/schemas/organisers_detail___schema1"
                          },
                          "additionalProperties": {
                            "$ref": "#/components/schemas/organisers_detail___schema2"
                          },
                          "description": "Profile URLs keyed by platform slug. Blank entries are stripped server-side."
                        },
                        "upcomingEvents": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/organisers_detail___schema3"
                          },
                          "description": "The organiser's public events that have not finished yet, soonest first."
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "slug",
                        "bio",
                        "logoUrl",
                        "logoVectorUrl",
                        "backgroundColor",
                        "textColor",
                        "website",
                        "socials",
                        "upcomingEvents"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "id": "b2d4a6f8-1c3e-4a5b-8d7f-2e9c0a1b3d45",
                    "name": "Riot Promotions",
                    "slug": "riot-promotions",
                    "bio": "Bristol promoters since 2011. Warehouse parties, one festival, and a stubborn dislike of queues.",
                    "logoUrl": "https://cdn.grofomo.com/riot-promotions/logo.png",
                    "logoVectorUrl": null,
                    "backgroundColor": "#080808",
                    "textColor": "#FAFAF7",
                    "website": "https://riotpromotions.example",
                    "socials": {
                      "instagram": "https://instagram.com/riotpromotions"
                    },
                    "upcomingEvents": [
                      {
                        "id": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                        "slug": "summer-fest",
                        "name": "Summer Fest",
                        "startDate": "2026-07-17",
                        "endDate": "2026-07-19",
                        "venueName": "Ashton Court",
                        "city": "Bristol",
                        "country": "GB",
                        "artworkUrl": "https://cdn.grofomo.com/summer-fest/brand/poster.jpg"
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such organiser, or they have not enabled a public profile.",
            "content": {
              "application/json": {
                "example": {
                  "apiVersion": 1,
                  "error": {
                    "code": "not_found",
                    "message": "No such organiser, or they have not enabled a public profile."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/search": {
      "get": {
        "operationId": "events_search",
        "summary": "Event search",
        "description": "Search events that organisers have opted in to public discovery, across the whole platform.\n\nCross-organisation, so it is not scoped to any one key and cannot be locked down per organiser. Meant for discovery surfaces rather than a single event's own website.\n\nNo key is required today because the Grofomo mobile app calls this endpoint without one — that will change. Send a key now: it is accepted and ignored, so the integration keeps working when enforcement lands.",
        "tags": [
          "Discovery"
        ],
        "security": [
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matched against event name and slug. Empty returns the soonest events.",
            "schema": {
              "type": "string"
            },
            "example": "summer"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results. Defaults to 20, capped at 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiVersion": {
                      "type": "number",
                      "const": 1
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "query": {
                          "type": "string"
                        },
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "slug": {
                                "type": "string",
                                "description": "Pass this to any of the per-event endpoints."
                              },
                              "name": {
                                "type": "string"
                              },
                              "startDate": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/events_search___schema0"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "First day, as `YYYY-MM-DD`."
                              },
                              "endDate": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/events_search___schema1"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "Last day, as `YYYY-MM-DD`."
                              },
                              "venueName": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "city": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "country": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/events_search___schema2"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "Raw ISO 3166-1 alpha-2 code, e.g. \"GB\"."
                              },
                              "artworkUrl": {
                                "anyOf": [
                                  {
                                    "$ref": "#/components/schemas/events_search___schema3"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "Poster or key artwork, for a listing card."
                              }
                            },
                            "required": [
                              "id",
                              "slug",
                              "name",
                              "startDate",
                              "endDate",
                              "venueName",
                              "city",
                              "country",
                              "artworkUrl"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "query",
                        "results"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "apiVersion",
                    "data"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "apiVersion": 1,
                  "data": {
                    "query": "summer",
                    "results": [
                      {
                        "id": "6f1c8e2a-4b3d-4c9e-9f21-8a7d5e0b1c34",
                        "slug": "summer-fest",
                        "name": "Summer Fest",
                        "startDate": "2026-07-17",
                        "endDate": "2026-07-19",
                        "venueName": "Ashton Court",
                        "city": "Bristol",
                        "country": "GB",
                        "artworkUrl": "https://cdn.grofomo.com/summer-fest/brand/poster.jpg"
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "ticket.sale": {
      "post": {
        "operationId": "webhook_ticket_sale",
        "summary": "Ticket sale",
        "description": "A paid ticket order completes (web, app, or door).\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "buyer_name": "Jane Smith",
                "buyer_first_name": "Jane",
                "buyer_email": "jane@example.com",
                "event_name": "Summer Fest",
                "items": "2× General Admission",
                "ticket_count": "2",
                "order_total": "£45.00",
                "order_number": "AB12CD",
                "message": "Jane Smith just purchased 2× General Admission for Summer Fest totalling £45.00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "merch.sale": {
      "post": {
        "operationId": "webhook_merch_sale",
        "summary": "Merch sale",
        "description": "A merch order is paid via checkout.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "buyer_name": "Jane Smith",
                "buyer_email": "jane@example.com",
                "event_name": "Summer Fest",
                "items": "1× Festival Tee (L, Black)",
                "item_count": "1",
                "order_total": "£28.00",
                "order_number": "M-4F7A2B",
                "message": "Jane Smith just bought 1× Festival Tee (L, Black) (Summer Fest merch) totalling £28.00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "ticket.refund": {
      "post": {
        "operationId": "webhook_ticket_refund",
        "summary": "Ticket refund",
        "description": "A ticket order is fully or partially refunded.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "buyer_name": "Jane Smith",
                "event_name": "Summer Fest",
                "order_number": "AB12CD",
                "refund_amount": "£22.50",
                "refund_total": "£22.50",
                "order_total": "£45.00",
                "refund_scope": "Partial",
                "message": "Partial refund on order AB12CD for Summer Fest (Jane Smith) - £22.50 refunded"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "merch.refund": {
      "post": {
        "operationId": "webhook_merch_refund",
        "summary": "Merch refund",
        "description": "A merch order is fully or partially refunded.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "buyer_name": "Jane Smith",
                "event_name": "Summer Fest",
                "order_number": "M-4F7A2B",
                "refund_total": "£28.00",
                "order_total": "£28.00",
                "refund_scope": "Full",
                "message": "Full refund on merch order M-4F7A2B (Jane Smith) - £28.00 refunded to date"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "comp.issued": {
      "post": {
        "operationId": "webhook_comp_issued",
        "summary": "Comps issued",
        "description": "Complimentary tickets are issued by the organiser team.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "guest_name": "Sam Rivera",
                "quantity": "2",
                "ticket_type": "General Admission",
                "event_name": "Summer Fest",
                "message": "2× comp ticket(s) issued to Sam Rivera for Summer Fest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "comp.redeemed": {
      "post": {
        "operationId": "webhook_comp_redeemed",
        "summary": "Artist comp redeemed",
        "description": "An artist sends a comp ticket from their remittance-offer allocation.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "artist_name": "DJ Nova",
                "recipient_name": "Sam Rivera",
                "event_name": "Summer Fest",
                "remaining": "3",
                "message": "DJ Nova sent a comp ticket to Sam Rivera for Summer Fest (3 left in their allocation)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "prelaunch.registration": {
      "post": {
        "operationId": "webhook_prelaunch_registration",
        "summary": "Prelaunch registration",
        "description": "An attendee registers interest on a prelaunch page.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "event_name": "Summer Fest 2027",
                "source_event_name": "Summer Fest 2026",
                "first_name": "Jane",
                "contact_email": "jane@example.com",
                "channels": "email, WhatsApp",
                "message": "New prelaunch registration for Summer Fest 2027"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    },
    "mailing_list.signup": {
      "post": {
        "operationId": "webhook_mailing_list_signup",
        "summary": "Mailing list signup",
        "description": "Someone joins the mailing list via a signup form.\n\nDelivered to the organiser's configured URL, signed with `X-Eventos-Signature: hmac-sha256=<hex>` over the raw body.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "The organiser's rendered message template."
                  }
                }
              },
              "example": {
                "event_name": "Summer Fest",
                "form_title": "Summer Fest mailing list",
                "contact_email": "jane@example.com",
                "message": "New mailing list signup for Summer Fest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Return quickly; slow handlers are retried."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "events_lineup___schema0": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Stable id for this set. Use it as a render key."
          },
          "stage": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema1"
              },
              {
                "type": "null"
              }
            ],
            "description": "Stage name. Null before a stage is assigned."
          },
          "day": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema2"
              },
              {
                "type": "null"
              }
            ],
            "description": "Festival day this set belongs to, as `YYYY-MM-DD`. An after-midnight set is attributed to the day it started, so a 01:00 slot sits under the previous date."
          },
          "startTime": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema3"
              },
              {
                "type": "null"
              }
            ],
            "description": "Local start time as `HH:MM`, in the event timezone."
          },
          "endTime": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema4"
              },
              {
                "type": "null"
              }
            ],
            "description": "Local end time as `HH:MM`, in the event timezone."
          },
          "durationMinutes": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema5"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set length in minutes."
          },
          "billingTier": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema6"
              },
              {
                "type": "null"
              }
            ],
            "description": "Organiser's billing label, e.g. \"headliner\". Free text; do not switch on it."
          },
          "isPlaceholder": {
            "type": "boolean",
            "description": "True for an unannounced set. Render `placeholderLabel` instead of an artist name."
          },
          "placeholderLabel": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema7"
              },
              {
                "type": "null"
              }
            ],
            "description": "What to show in place of a name, e.g. \"Sunday headliner\"."
          },
          "placeholderDescription": {
            "default": null,
            "description": "Longer copy for a placeholder, e.g. when it will be announced.",
            "$ref": "#/components/schemas/events_lineup___schema8"
          },
          "featured": {
            "type": "boolean",
            "description": "The organiser has flagged this set as a highlight."
          },
          "createdAtIso": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema9"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the slot was first created."
          },
          "artist": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema10"
              },
              {
                "type": "null"
              }
            ],
            "description": "The lead artist, and the first entry of `artists`. Null on a placeholder slot."
          },
          "artists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_lineup___schema10"
            },
            "description": "Every artist on the slot, in billing order. A back-to-back set has two or more; a normal set has exactly one; a placeholder has none."
          }
        },
        "required": [
          "id",
          "stage",
          "day",
          "startTime",
          "endTime",
          "durationMinutes",
          "billingTier",
          "isPlaceholder",
          "placeholderLabel",
          "placeholderDescription",
          "featured",
          "createdAtIso",
          "artist",
          "artists"
        ],
        "additionalProperties": false
      },
      "events_lineup___schema1": {
        "type": "string"
      },
      "events_lineup___schema2": {
        "type": "string"
      },
      "events_lineup___schema3": {
        "type": "string"
      },
      "events_lineup___schema4": {
        "type": "string"
      },
      "events_lineup___schema5": {
        "type": "number"
      },
      "events_lineup___schema6": {
        "type": "string"
      },
      "events_lineup___schema7": {
        "type": "string"
      },
      "events_lineup___schema8": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_lineup___schema9": {
        "type": "string"
      },
      "events_lineup___schema10": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Pass this to the artist-detail endpoint for the full profile."
          },
          "name": {
            "type": "string",
            "description": "Billed name, as the organiser wants it displayed."
          },
          "imageUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema11"
              },
              {
                "type": "null"
              }
            ],
            "description": "Press shot. Null when the artist has none."
          },
          "genres": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_lineup___schema12"
            },
            "description": "Free-text genre tags. May be empty."
          },
          "hometown": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema13"
              },
              {
                "type": "null"
              }
            ],
            "description": "City."
          },
          "region": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema14"
              },
              {
                "type": "null"
              }
            ],
            "description": "State or province."
          },
          "country": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema15"
              },
              {
                "type": "null"
              }
            ],
            "description": "Raw ISO 3166-1 alpha-2 code, e.g. \"GB\"."
          },
          "locationLabel": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_lineup___schema16"
              },
              {
                "type": "null"
              }
            ],
            "description": "Pre-formatted location for display, e.g. \"Bristol, England, United Kingdom\". Render this rather than joining the parts yourself."
          }
        },
        "required": [
          "id",
          "name",
          "imageUrl",
          "genres",
          "hometown",
          "region",
          "country",
          "locationLabel"
        ],
        "additionalProperties": false
      },
      "events_lineup___schema11": {
        "type": "string"
      },
      "events_lineup___schema12": {
        "type": "string"
      },
      "events_lineup___schema13": {
        "type": "string"
      },
      "events_lineup___schema14": {
        "type": "string"
      },
      "events_lineup___schema15": {
        "type": "string"
      },
      "events_lineup___schema16": {
        "type": "string"
      },
      "events_lineup___schema17": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "events_lineup___schema18": {
        "type": "string"
      },
      "events_artist___schema0": {
        "type": "string"
      },
      "events_artist___schema1": {
        "type": "string"
      },
      "events_artist___schema2": {
        "type": "string"
      },
      "events_artist___schema3": {
        "type": "string"
      },
      "events_artist___schema4": {
        "type": "string"
      },
      "events_artist___schema5": {
        "type": "string"
      },
      "events_artist___schema6": {
        "type": "string"
      },
      "events_artist___schema7": {
        "type": "string"
      },
      "events_artist___schema8": {
        "type": "string"
      },
      "events_artist___schema9": {
        "type": "string"
      },
      "events_artist___schema10": {
        "type": "string"
      },
      "events_artist___schema11": {
        "type": "string"
      },
      "events_artist___schema12": {
        "type": "object",
        "properties": {
          "lineupId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Matches the `id` of the corresponding lineup slot."
          },
          "stage": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "day": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_artist___schema13"
              },
              {
                "type": "null"
              }
            ],
            "description": "Festival day as `YYYY-MM-DD`."
          },
          "startTime": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_artist___schema14"
              },
              {
                "type": "null"
              }
            ],
            "description": "Local start time as `HH:MM`."
          },
          "endTime": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_artist___schema15"
              },
              {
                "type": "null"
              }
            ],
            "description": "Local end time as `HH:MM`."
          }
        },
        "required": [
          "lineupId",
          "stage",
          "day",
          "startTime",
          "endTime"
        ],
        "additionalProperties": false
      },
      "events_artist___schema13": {
        "type": "string"
      },
      "events_artist___schema14": {
        "type": "string"
      },
      "events_artist___schema15": {
        "type": "string"
      },
      "events_faq___schema0": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_faq___schema1"
              },
              {
                "type": "null"
              }
            ],
            "description": "Category id. Null for the catch-all group of uncategorised entries."
          },
          "name": {
            "type": "string",
            "description": "Category heading, e.g. \"Getting there\"."
          },
          "faqs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_faq___schema2"
            },
            "description": "The questions in this category, in the organiser's order."
          }
        },
        "required": [
          "id",
          "name",
          "faqs"
        ],
        "additionalProperties": false
      },
      "events_faq___schema1": {
        "type": "string"
      },
      "events_faq___schema2": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "question": {
            "type": "string"
          },
          "answerDoc": {
            "description": "The answer as a ProseMirror document. Only useful if you render rich text yourself — otherwise use `answerHtml`.",
            "$ref": "#/components/schemas/events_faq___schema3"
          },
          "answerPlain": {
            "type": "string",
            "description": "Plain text, for search indexes and JSON-LD."
          },
          "answerHtml": {
            "type": "string",
            "description": "The answer pre-rendered to sanitised HTML. Safe to inject directly, and always present."
          },
          "updatedAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "question",
          "answerDoc",
          "answerPlain",
          "answerHtml",
          "updatedAt"
        ],
        "additionalProperties": false
      },
      "events_faq___schema3": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "attrs": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          },
          "marks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "attrs": {
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                }
              },
              "required": [
                "type"
              ],
              "additionalProperties": false
            }
          },
          "content": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_faq___schema3"
            }
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": false
      },
      "events_faq___schema4": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "events_faq___schema5": {
        "type": "string"
      },
      "events_news___schema0": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Pass this to the single-article endpoint for the body."
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string",
            "description": "Short standfirst, for a listing card."
          },
          "heroImageUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_news___schema1"
            },
            "description": "Free-text tags, e.g. \"lineup\". May be empty."
          },
          "author": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "publishedAt": {
            "type": "string",
            "description": "Publication time. The index is sorted newest first on this."
          },
          "updatedAt": {
            "type": "string"
          }
        },
        "required": [
          "slug",
          "title",
          "excerpt",
          "heroImageUrl",
          "tags",
          "author",
          "publishedAt",
          "updatedAt"
        ],
        "additionalProperties": false
      },
      "events_news___schema1": {
        "type": "string"
      },
      "events_news___schema2": {
        "type": "string"
      },
      "events_news_article___schema0": {
        "type": "string"
      },
      "events_news_article___schema1": {
        "type": "string"
      },
      "events_news_article___schema2": {
        "type": "string"
      },
      "events_news_article___schema3": {
        "type": "string"
      },
      "events_news_article___schema4": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "events_broadcasts___schema0": {
        "type": "string"
      },
      "events_tickets___schema0": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Referenced by `categoryId` on a ticket type."
          },
          "title": {
            "type": "string",
            "description": "Section heading, e.g. \"Weekend\"."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "description"
        ],
        "additionalProperties": false
      },
      "events_tickets___schema1": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "categoryId": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema2"
              },
              {
                "type": "null"
              }
            ],
            "description": "Groups this type under a category. Null when ungrouped."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema3"
              },
              {
                "type": "null"
              }
            ],
            "description": "Plain-text description, for clients that render no rich text."
          },
          "descriptionDoc": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema4"
              },
              {
                "type": "null"
              }
            ],
            "description": "The same description as a ProseMirror document. Render this when present, otherwise `description`."
          },
          "ticketsPerGroup": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Admissions issued per unit bought. 1 is a single ticket; higher means a table or group ticket."
          },
          "minPerOrder": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Fewest units one order may contain."
          },
          "maxPerOrder": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Most units one order may contain."
          },
          "isHighlighted": {
            "type": "boolean",
            "description": "The organiser wants this type visually promoted."
          },
          "highlightMessage": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema8"
              },
              {
                "type": "null"
              }
            ],
            "description": "Badge text for a highlighted type, e.g. \"Most popular\"."
          },
          "paymentPlan": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema9"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when this type can be bought on a payment plan. Null otherwise."
          },
          "releases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/events_tickets___schema10"
            },
            "description": "Price tiers, in sale order. Only tiers this caller is allowed to see are included."
          }
        },
        "required": [
          "id",
          "categoryId",
          "title",
          "description",
          "descriptionDoc",
          "ticketsPerGroup",
          "minPerOrder",
          "maxPerOrder",
          "isHighlighted",
          "highlightMessage",
          "paymentPlan",
          "releases"
        ],
        "additionalProperties": false
      },
      "events_tickets___schema2": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "events_tickets___schema3": {
        "type": "string"
      },
      "events_tickets___schema4": {
        "type": "object",
        "properties": {
          "type": {
            "$ref": "#/components/schemas/events_tickets___schema5"
          },
          "content": {
            "$ref": "#/components/schemas/events_tickets___schema6"
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": false
      },
      "events_tickets___schema5": {
        "type": "string",
        "const": "doc"
      },
      "events_tickets___schema6": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/events_tickets___schema7"
        }
      },
      "events_tickets___schema7": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "text"
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 4000
              },
              "marks": {
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "const": "link"
                        },
                        "attrs": {
                          "type": "object",
                          "properties": {
                            "href": {
                              "type": "string"
                            },
                            "target": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "rel": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "class": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "title": {
                              "anyOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            }
                          },
                          "required": [
                            "href"
                          ],
                          "additionalProperties": false
                        }
                      },
                      "required": [
                        "type",
                        "attrs"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "bold",
                            "italic",
                            "underline",
                            "strike",
                            "code"
                          ]
                        },
                        "attrs": {
                          "type": "object",
                          "propertyNames": {
                            "type": "string"
                          },
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "type"
                      ],
                      "additionalProperties": false
                    }
                  ]
                }
              }
            },
            "required": [
              "type",
              "text"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "paragraph"
              },
              "attrs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/events_tickets___schema7"
                }
              }
            },
            "required": [
              "type"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "heading"
              },
              "attrs": {
                "type": "object",
                "properties": {
                  "level": {
                    "anyOf": [
                      {
                        "type": "number",
                        "const": 2
                      },
                      {
                        "type": "number",
                        "const": 3
                      }
                    ]
                  }
                },
                "required": [
                  "level"
                ],
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/events_tickets___schema7"
                }
              }
            },
            "required": [
              "type",
              "attrs"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "bulletList",
                  "orderedList"
                ]
              },
              "attrs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/events_tickets___schema7"
                }
              }
            },
            "required": [
              "type"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "listItem"
              },
              "attrs": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/events_tickets___schema7"
                }
              }
            },
            "required": [
              "type"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "hardBreak"
              }
            },
            "required": [
              "type"
            ],
            "additionalProperties": false
          }
        ]
      },
      "events_tickets___schema8": {
        "type": "string"
      },
      "events_tickets___schema9": {
        "type": "object",
        "properties": {
          "depositBps": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Deposit as basis points of the total, so 2500 is 25%."
          },
          "installments": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Number of payments after the deposit."
          }
        },
        "required": [
          "depositBps",
          "installments"
        ],
        "additionalProperties": false
      },
      "events_tickets___schema10": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "label": {
            "type": "string",
            "description": "Release name, e.g. \"Tier 2\" or \"Final release\"."
          },
          "pricePennies": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Price in minor units, so 9500 is £95.00."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code, e.g. \"GBP\"."
          },
          "available": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_tickets___schema11"
              },
              {
                "type": "null"
              }
            ],
            "description": "Tickets left. Null means EITHER unlimited OR the organiser has chosen not to publish counts — so this can never gate a buy button on its own. Use `soldOut`."
          },
          "capacity": {
            "description": "Total allocation, hidden under the same rule as `available`. With it you can show percent sold.",
            "$ref": "#/components/schemas/events_tickets___schema12"
          },
          "closesAt": {
            "description": "When this release stops selling. Null means no cut-off.",
            "$ref": "#/components/schemas/events_tickets___schema13"
          },
          "opensAt": {
            "description": "When this release starts selling. Set alongside `upcoming`, and the target for a countdown.",
            "$ref": "#/components/schemas/events_tickets___schema14"
          },
          "upcoming": {
            "description": "A teaser: listed but not yet purchasable. Takes precedence over `soldOut` and `closed`, which are both false while it holds.",
            "$ref": "#/components/schemas/events_tickets___schema15"
          },
          "soldOut": {
            "type": "boolean",
            "description": "Authoritative. Gate your buy button on this, never on `available`."
          },
          "closed": {
            "type": "boolean",
            "description": "The sale window has ended. Still shown, no longer purchasable."
          }
        },
        "required": [
          "id",
          "label",
          "pricePennies",
          "currency",
          "available",
          "soldOut",
          "closed"
        ],
        "additionalProperties": false
      },
      "events_tickets___schema11": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "events_tickets___schema12": {
        "anyOf": [
          {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          {
            "type": "null"
          }
        ]
      },
      "events_tickets___schema13": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_tickets___schema14": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_tickets___schema15": {
        "type": "boolean"
      },
      "events_tickets___schema16": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "name": {
              "type": "string",
              "description": "Organiser-facing name, e.g. \"Group of four\"."
            },
            "kind": {
              "type": "string",
              "enum": [
                "tiered_percent",
                "tiered_fixed",
                "buy_n_get_m",
                "bundle_price"
              ],
              "description": "Which of the fields below apply, and how `tiers[].value` should be read."
            },
            "applicableReleaseIds": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/events_tickets___schema17"
              },
              "description": "Releases this rule applies to. Empty means the whole order."
            },
            "tiers": {
              "description": "Present for the two tiered kinds.",
              "$ref": "#/components/schemas/events_tickets___schema18"
            },
            "buyQuantity": {
              "description": "The \"buy N\" of a `buy_n_get_m` rule.",
              "$ref": "#/components/schemas/events_tickets___schema19"
            },
            "freeQuantity": {
              "description": "The \"get M free\" of a `buy_n_get_m` rule.",
              "$ref": "#/components/schemas/events_tickets___schema20"
            },
            "bundleSize": {
              "description": "Tickets per bundle, for `bundle_price`.",
              "$ref": "#/components/schemas/events_tickets___schema21"
            },
            "bundlePricePennies": {
              "description": "Price of one whole bundle, for `bundle_price`.",
              "$ref": "#/components/schemas/events_tickets___schema22"
            },
            "combinableWithPromo": {
              "type": "boolean",
              "description": "Whether a promo code can stack on top of this discount."
            }
          },
          "required": [
            "id",
            "name",
            "kind",
            "applicableReleaseIds",
            "combinableWithPromo"
          ],
          "additionalProperties": false
        }
      },
      "events_tickets___schema17": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "events_tickets___schema18": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "minQuantity": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Quantity at which this tier starts applying."
            },
            "value": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Basis points off for `tiered_percent` (1000 = 10%), or pennies off per ticket for `tiered_fixed`."
            }
          },
          "required": [
            "minQuantity",
            "value"
          ],
          "additionalProperties": false
        }
      },
      "events_tickets___schema19": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "events_tickets___schema20": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "events_tickets___schema21": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "events_tickets___schema22": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "events_merch___schema0": {
        "type": "string"
      },
      "events_merch___schema1": {
        "type": "number"
      },
      "events_merch___schema2": {
        "type": "string"
      },
      "events_merch___schema3": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Colourway name, e.g. \"Bone\"."
          },
          "hex": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_merch___schema4"
              },
              {
                "type": "null"
              }
            ],
            "description": "Swatch colour, for a colour picker."
          },
          "frontImageUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_merch___schema5"
              },
              {
                "type": "null"
              }
            ],
            "description": "Plain garment front, with no logo applied."
          },
          "backImageUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_merch___schema6"
              },
              {
                "type": "null"
              }
            ],
            "description": "Plain garment back, with no logo applied."
          },
          "logoColor": {
            "description": "How to treat the logo on this colourway: `auto`, `brand`, `white` or `black`.",
            "$ref": "#/components/schemas/events_merch___schema7"
          },
          "outOfStock": {
            "description": "Show the colourway, but do not let anyone buy it.",
            "$ref": "#/components/schemas/events_merch___schema8"
          }
        },
        "required": [
          "label",
          "hex",
          "frontImageUrl",
          "backImageUrl"
        ],
        "additionalProperties": false
      },
      "events_merch___schema4": {
        "type": "string"
      },
      "events_merch___schema5": {
        "type": "string"
      },
      "events_merch___schema6": {
        "type": "string"
      },
      "events_merch___schema7": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_merch___schema8": {
        "type": "boolean"
      },
      "events_merch___schema9": {
        "type": "object",
        "properties": {
          "side": {
            "type": "string",
            "enum": [
              "front",
              "back"
            ]
          },
          "x": {
            "type": "number",
            "description": "Centre of the logo, as a 0–1 fraction of the garment image width."
          },
          "y": {
            "type": "number",
            "description": "Centre of the logo, as a 0–1 fraction of the garment image height."
          },
          "scale": {
            "type": "number",
            "description": "Logo width, as a 0–1 fraction of the garment image width."
          },
          "recolor": {
            "type": "boolean"
          },
          "logoUrl": {
            "description": "A logo specific to this placement. Absent means use the event logo.",
            "$ref": "#/components/schemas/events_merch___schema10"
          },
          "logoColor": {
            "description": "`auto`, `brand`, `white` or `black`.",
            "$ref": "#/components/schemas/events_merch___schema11"
          },
          "clip": {
            "description": "Mask the logo to the garment silhouette, so a wide placement does not bleed off the edge.",
            "$ref": "#/components/schemas/events_merch___schema12"
          }
        },
        "required": [
          "side",
          "x",
          "y",
          "scale"
        ],
        "additionalProperties": false
      },
      "events_merch___schema10": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_merch___schema11": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "events_merch___schema12": {
        "type": "boolean"
      },
      "events_merch___schema13": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            }
          },
          "required": [
            "x",
            "y"
          ],
          "additionalProperties": false
        }
      },
      "forms_definition___schema0": {
        "type": "string",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
      },
      "forms_definition___schema1": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "description": "Key this answer under this id when you submit."
          },
          "type": {
            "type": "string",
            "enum": [
              "rating",
              "radio",
              "checkbox",
              "select",
              "textarea",
              "text",
              "email",
              "tel",
              "number",
              "artist_multiselect"
            ],
            "description": "Which control to render."
          },
          "label": {
            "type": "string",
            "maxLength": 300,
            "description": "The question, as the organiser wrote it."
          },
          "help": {
            "description": "Hint text to show under the control.",
            "$ref": "#/components/schemas/forms_definition___schema2"
          },
          "required": {
            "description": "Absent means optional.",
            "$ref": "#/components/schemas/forms_definition___schema3"
          },
          "role": {
            "description": "What the answer means to Grofomo, e.g. `email` promotes it to the contact address.",
            "$ref": "#/components/schemas/forms_definition___schema4"
          },
          "options": {
            "description": "Choices for a radio, checkbox, select or artist picker. Submit the `value`, display the `label`.",
            "$ref": "#/components/schemas/forms_definition___schema5"
          },
          "min": {
            "description": "Lower bound of a rating.",
            "$ref": "#/components/schemas/forms_definition___schema7"
          },
          "max": {
            "description": "Upper bound of a rating.",
            "$ref": "#/components/schemas/forms_definition___schema8"
          },
          "source": {
            "type": "string",
            "enum": [
              "lineup",
              "stages",
              "merch_items"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "label"
        ],
        "additionalProperties": false
      },
      "forms_definition___schema2": {
        "type": "string",
        "maxLength": 500
      },
      "forms_definition___schema3": {
        "type": "boolean"
      },
      "forms_definition___schema4": {
        "anyOf": [
          {
            "type": "string",
            "enum": [
              "overall_rating",
              "venue_rating",
              "would_return",
              "favourite_artist",
              "favourite_stage",
              "email",
              "name",
              "phone",
              "merch_item",
              "merch_colour",
              "merch_size",
              "quantity"
            ]
          },
          {
            "type": "null"
          }
        ]
      },
      "forms_definition___schema5": {
        "maxItems": 200,
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/forms_definition___schema6"
        }
      },
      "forms_definition___schema6": {
        "type": "object",
        "properties": {
          "value": {
            "type": "string",
            "maxLength": 200
          },
          "label": {
            "type": "string",
            "maxLength": 300
          }
        },
        "required": [
          "value",
          "label"
        ],
        "additionalProperties": false
      },
      "forms_definition___schema7": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "forms_definition___schema8": {
        "type": "integer",
        "minimum": -9007199254740991,
        "maximum": 9007199254740991
      },
      "forms_definition___schema9": {
        "type": "string"
      },
      "forms_definition___schema10": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Colourway name, e.g. \"Bone\"."
          },
          "hex": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/forms_definition___schema11"
              },
              {
                "type": "null"
              }
            ],
            "description": "Swatch colour, for a colour picker."
          },
          "frontImageUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/forms_definition___schema12"
              },
              {
                "type": "null"
              }
            ],
            "description": "Plain garment front, with no logo applied."
          },
          "backImageUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/forms_definition___schema13"
              },
              {
                "type": "null"
              }
            ],
            "description": "Plain garment back, with no logo applied."
          },
          "logoColor": {
            "description": "How to treat the logo on this colourway: `auto`, `brand`, `white` or `black`.",
            "$ref": "#/components/schemas/forms_definition___schema14"
          },
          "outOfStock": {
            "description": "Show the colourway, but do not let anyone buy it.",
            "$ref": "#/components/schemas/forms_definition___schema15"
          }
        },
        "required": [
          "label",
          "hex",
          "frontImageUrl",
          "backImageUrl"
        ],
        "additionalProperties": false
      },
      "forms_definition___schema11": {
        "type": "string"
      },
      "forms_definition___schema12": {
        "type": "string"
      },
      "forms_definition___schema13": {
        "type": "string"
      },
      "forms_definition___schema14": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "forms_definition___schema15": {
        "type": "boolean"
      },
      "forms_definition___schema16": {
        "type": "object",
        "properties": {
          "side": {
            "type": "string",
            "enum": [
              "front",
              "back"
            ]
          },
          "x": {
            "type": "number",
            "description": "Centre of the logo, as a 0–1 fraction of the garment image width."
          },
          "y": {
            "type": "number",
            "description": "Centre of the logo, as a 0–1 fraction of the garment image height."
          },
          "scale": {
            "type": "number",
            "description": "Logo width, as a 0–1 fraction of the garment image width."
          },
          "recolor": {
            "type": "boolean"
          },
          "logoUrl": {
            "description": "A logo specific to this placement. Absent means use the event logo.",
            "$ref": "#/components/schemas/forms_definition___schema17"
          },
          "logoColor": {
            "description": "`auto`, `brand`, `white` or `black`.",
            "$ref": "#/components/schemas/forms_definition___schema18"
          },
          "clip": {
            "description": "Mask the logo to the garment silhouette, so a wide placement does not bleed off the edge.",
            "$ref": "#/components/schemas/forms_definition___schema19"
          }
        },
        "required": [
          "side",
          "x",
          "y",
          "scale"
        ],
        "additionalProperties": false
      },
      "forms_definition___schema17": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "forms_definition___schema18": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ]
      },
      "forms_definition___schema19": {
        "type": "boolean"
      },
      "forms_definition___schema20": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            }
          },
          "required": [
            "x",
            "y"
          ],
          "additionalProperties": false
        }
      },
      "events_detail___schema0": {
        "type": "string"
      },
      "events_detail___schema1": {
        "type": "string"
      },
      "events_detail___schema2": {
        "type": "string"
      },
      "events_detail___schema3": {
        "type": "string"
      },
      "events_detail___schema4": {
        "type": "string"
      },
      "events_detail___schema5": {
        "type": "string"
      },
      "events_detail___schema6": {
        "type": "string"
      },
      "events_detail___schema7": {
        "type": "string"
      },
      "events_detail___schema8": {
        "type": "string"
      },
      "events_detail___schema9": {
        "type": "string"
      },
      "events_detail___schema10": {
        "type": "string"
      },
      "events_detail___schema11": {
        "type": "string"
      },
      "events_detail___schema12": {
        "type": "string"
      },
      "events_detail___schema13": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable within the event. Matches `stage` on a lineup slot by name, not by id."
          },
          "name": {
            "type": "string"
          },
          "type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_detail___schema14"
              },
              {
                "type": "null"
              }
            ],
            "description": "Free text, e.g. \"tent\"."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "imageUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "description",
          "imageUrl"
        ],
        "additionalProperties": false
      },
      "events_detail___schema14": {
        "type": "string"
      },
      "events_detail___schema15": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/events_detail___schema16"
        },
        "description": "Hex colours, most important first."
      },
      "events_detail___schema16": {
        "type": "string"
      },
      "events_detail___schema17": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema18"
          },
          {
            "type": "null"
          }
        ],
        "description": "Raster logo (PNG/JPG). Null when the mark is SVG-only."
      },
      "events_detail___schema18": {
        "type": "string"
      },
      "events_detail___schema19": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema20"
          },
          {
            "type": "null"
          }
        ],
        "description": "Hosted SVG logo. Prefer this on the web when set, and fall back to `logoUrl`."
      },
      "events_detail___schema20": {
        "type": "string"
      },
      "events_detail___schema21": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema22"
          },
          {
            "type": "null"
          }
        ],
        "description": "Poster or key artwork."
      },
      "events_detail___schema22": {
        "type": "string"
      },
      "events_detail___schema23": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema24"
          },
          {
            "type": "null"
          }
        ],
        "description": "Wide cover image, for a page header."
      },
      "events_detail___schema24": {
        "type": "string"
      },
      "events_detail___schema25": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema26"
          },
          {
            "type": "null"
          }
        ],
        "description": "Hex page background the organiser chose."
      },
      "events_detail___schema26": {
        "type": "string"
      },
      "events_detail___schema27": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema28"
          },
          {
            "type": "null"
          }
        ],
        "description": "Hex foreground paired with `backgroundColor`."
      },
      "events_detail___schema28": {
        "type": "string"
      },
      "events_detail___schema29": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema30"
          },
          {
            "type": "null"
          }
        ],
        "description": "Hex accent for buttons and CTAs. Null means fall back to `brandColors[0]`."
      },
      "events_detail___schema30": {
        "type": "string"
      },
      "events_detail___schema31": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema32"
          },
          {
            "type": "null"
          }
        ],
        "description": "Font name. See `fonts` for something you can actually load."
      },
      "events_detail___schema32": {
        "type": "string"
      },
      "events_detail___schema33": {
        "type": "object",
        "propertyNames": {
          "$ref": "#/components/schemas/events_detail___schema34"
        },
        "additionalProperties": {
          "$ref": "#/components/schemas/events_detail___schema35"
        },
        "description": "The event's social profile URLs, keyed by platform slug."
      },
      "events_detail___schema34": {
        "type": "string"
      },
      "events_detail___schema35": {
        "type": "string"
      },
      "events_detail___schema36": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/events_detail___schema37"
          },
          {
            "type": "null"
          }
        ],
        "description": "Site map image, when the organiser has uploaded one."
      },
      "events_detail___schema37": {
        "type": "string"
      },
      "events_detail___schema38": {
        "default": "neutral",
        "type": "string",
        "enum": [
          "neutral",
          "brand",
          "brand-full"
        ]
      },
      "events_detail___schema39": {
        "default": {
          "headline": null,
          "body": null
        },
        "type": "object",
        "properties": {
          "headline": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_detail___schema40"
              },
              {
                "type": "null"
              }
            ]
          },
          "body": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/events_detail___schema40"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "headline",
          "body"
        ],
        "additionalProperties": false
      },
      "events_detail___schema40": {
        "type": "object",
        "properties": {
          "family": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "weight": {
            "type": "number"
          }
        },
        "required": [
          "family",
          "url",
          "weight"
        ],
        "additionalProperties": false
      },
      "events_detail___schema41": {
        "type": "string"
      },
      "events_detail___schema42": {
        "type": "string",
        "enum": [
          "whatsapp",
          "telegram",
          "discord",
          "signal",
          "other"
        ]
      },
      "events_detail___schema43": {
        "type": "string"
      },
      "events_detail___schema44": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "logoUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "logoUrl"
        ],
        "additionalProperties": false
      },
      "organisers_detail___schema0": {
        "type": "string"
      },
      "organisers_detail___schema1": {
        "type": "string"
      },
      "organisers_detail___schema2": {
        "type": "string"
      },
      "organisers_detail___schema3": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "slug": {
            "type": "string",
            "description": "Pass this to any of the per-event endpoints."
          },
          "name": {
            "type": "string"
          },
          "startDate": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/organisers_detail___schema4"
              },
              {
                "type": "null"
              }
            ],
            "description": "First day, as `YYYY-MM-DD`."
          },
          "endDate": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/organisers_detail___schema5"
              },
              {
                "type": "null"
              }
            ],
            "description": "Last day, as `YYYY-MM-DD`."
          },
          "venueName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "city": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "country": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/organisers_detail___schema6"
              },
              {
                "type": "null"
              }
            ],
            "description": "Raw ISO 3166-1 alpha-2 code, e.g. \"GB\"."
          },
          "artworkUrl": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/organisers_detail___schema7"
              },
              {
                "type": "null"
              }
            ],
            "description": "Poster or key artwork, for a listing card."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "startDate",
          "endDate",
          "venueName",
          "city",
          "country",
          "artworkUrl"
        ],
        "additionalProperties": false
      },
      "organisers_detail___schema4": {
        "type": "string"
      },
      "organisers_detail___schema5": {
        "type": "string"
      },
      "organisers_detail___schema6": {
        "type": "string"
      },
      "organisers_detail___schema7": {
        "type": "string"
      },
      "events_search___schema0": {
        "type": "string"
      },
      "events_search___schema1": {
        "type": "string"
      },
      "events_search___schema2": {
        "type": "string"
      },
      "events_search___schema3": {
        "type": "string"
      }
    },
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Grofomo-Key",
        "description": "A publishable (`pk_live_…`) or secret (`sk_live_…`) key. Publishable keys are safe in browser code; secret keys are server-only and are refused if passed as a `?key=` query parameter."
      }
    }
  }
}