{
  "$schema": "./_mapping.schema.json",
  "provider": "eventbrite",
  "name": "Eventbrite",
  "vertical": "tickets",
  "verified_on": "2026-10-13",
  "docs": [
    {
      "title": "Eventbrite API v3 Reference",
      "url": "https://www.eventbrite.com/platform/api",
      "note": "Public. Object field tables (Event, Ticket Class, Order, Attendee), Basic Types (Currency, Datetime, Datetime with Timezone) and endpoint list. The page is client-rendered; the field tables are in the page's embedded data and quoted below by heading anchor."
    },
    {
      "title": "Eventbrite Webhooks guide",
      "url": "https://www.eventbrite.com/platform/docs/webhooks",
      "note": "Public, client-rendered (text lives in the platform JS bundle). Lists the six objects and their actions, the POST/JSON composition, the {api_url, config.endpoint_url} payload, retries every ten minutes up to ten more attempts, and that a 410 response unsubscribes the endpoint. Says nothing about signing."
    },
    {
      "title": "Eventbrite Authentication",
      "url": "https://www.eventbrite.com/platform/docs/authentication",
      "note": "OAuth 2 (authorize at https://www.eventbrite.com/oauth/authorize, token at https://www.eventbrite.com/oauth/token); send the token as Authorization: Bearer or ?token=."
    }
  ],
  "access": {
    "public_docs": true,
    "how_to_get_access": "Self-serve: any Eventbrite account creates an API key at https://www.eventbrite.com/platform/api-keys. For a venue's own events, the venue authorises SplitClub's app through Eventbrite OAuth (or gives SplitClub a private token). Webhooks are created per organization with POST /organizations/{organization_id}/webhooks/ or in Account Settings → Webhooks.",
    "auth": "OAuth 2 bearer token: Authorization: Bearer <token> (or ?token=<token>).",
    "base_url": "https://www.eventbriteapi.com/v3"
  },
  "splitclub": {
    "integration_provider": null,
    "recommended_path": "poll_and_post",
    "note": "SplitClub's inbound hook does not accept an 'eventbrite' provider yet. Until it does, a SplitClub-side worker holding the venue's OAuth token lists the organization's events (GET /organizations/{organization_id}/events/) and their ticket classes (GET /events/{event_id}/ticket_classes/), and POSTs them to SplitClub as Events with tiers (plus one ticketed_event Offering per recurring show). The webhook payload is only an api_url, so adding 'eventbrite' to the inbound hook later means: receive {api_url}, check its host is www.eventbriteapi.com, GET it with the venue's token, then run the same mapping. Eventbrite's public API has no endpoint that creates an order or issues tickets, so a funded SplitClub Checkout cannot be turned into Eventbrite tickets by API (see gaps)."
  },
  "objects": {
    "Offering": {
      "source": "GET /events/{event_id}/ (use the series parent for a recurring show, is_series = true)",
      "kind": "ticketed_event",
      "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields",
      "note": "Only needed when a show recurs (series parent) or when the venue wants one Offering per show; each dated night is an Event (below) that points at it with offering_external_id. Pointers are rooted at the SplitClub Offering; tiers come from the Ticket Class map (PriceTier).",
      "fields": [
        { "to": "/external_id", "from": "id (the Event ID used as {event_id} in GET /events/{event_id}/)", "transform": "string → prefix eb_evt_", "source_url": "https://www.eventbrite.com/platform/api#section-ref-event" },
        { "to": "/kind", "from": "const:ticketed_event" },
        { "to": "/title", "from": "name.text", "transform": "multipart-text {text, html}: take text, trim, truncate to 120 chars", "source_url": "https://www.eventbrite.com/platform/api#basic-types--multipart-text" },
        { "to": "/description", "from": "summary", "transform": "plain string; description is DEPRECATED and the full HTML lives at GET /events/{event_id}/description/", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" },
        { "to": "/booking_url", "from": "url", "transform": "URL of the event's listing page on eventbrite.com; must be https", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" },
        { "to": "/tiers", "from": "GET /events/{event_id}/ticket_classes/ → ticket_classes[]", "transform": "one tier per ticket class, see PriceTier", "source_url": "https://www.eventbrite.com/platform/api#section-ref-ticket-class" },
        { "to": "/policy/min_age", "from": "music_properties.age_restriction (expansion music_properties)", "transform": "enum values are not listed in the docs; map an 18+ or 21+ value to 18 / 21, else omit", "source_url": "https://www.eventbrite.com/platform/api#music_properties_object", "unverified": true }
      ]
    },
    "Event": {
      "source": "GET /events/{event_id}/ (one per dated occurrence; list with GET /organizations/{organization_id}/events/)",
      "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields",
      "note": "Skip events whose status is draft, canceled, ended or completed (status: draft, live, started, ended, completed, canceled). capacity is a private field, readable only with the organization's own token.",
      "fields": [
        { "to": "/external_id", "from": "id (the Event ID)", "transform": "string → prefix eb_evt_", "source_url": "https://www.eventbrite.com/platform/api#section-ref-event" },
        { "to": "/offering_external_id", "from": "series_id (series occurrence) or the event's own id", "transform": "prefix eb_evt_; only when a ticketed_event Offering was posted for it", "source_url": "https://www.eventbrite.com/platform/api#section-ref-event-series", "unverified": true },
        { "to": "/title", "from": "name.text", "transform": "truncate to 120 chars", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" },
        { "to": "/description", "from": "summary", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" },
        { "to": "/starts_at", "from": "start (datetime-tz: {timezone, utc, local})", "transform": "start.utc is ISO 8601 with Z, already valid. For a local offset: parse start.local (naive) in IANA start.timezone and emit with its offset, e.g. 2018-05-11T19:00:00-07:00", "source_url": "https://www.eventbrite.com/platform/api#basic-types--datetime-with-timezone" },
        { "to": "/ends_at", "from": "end (datetime-tz)", "transform": "same as starts_at", "source_url": "https://www.eventbrite.com/platform/api#basic-types--datetime-with-timezone" },
        { "to": "/capacity", "from": "capacity", "transform": "integer; private field (organization token). Sum of ticket classes' capacity unless capacity_is_custom", "source_url": "https://www.eventbrite.com/platform/api#ref-event--private-fields" },
        { "to": "/tiers", "from": "GET /events/{event_id}/ticket_classes/ → ticket_classes[]", "transform": "see PriceTier", "source_url": "https://www.eventbrite.com/platform/api#section-ref-ticket-class" },
        { "to": "/on_sale/starts_at", "from": "ticket_classes[].sales_start", "transform": "earliest across the event's ticket classes; type datetime = ISO 8601 UTC (…Z)", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--private-fields" },
        { "to": "/on_sale/ends_at", "from": "ticket_classes[].sales_end", "transform": "latest across the event's ticket classes; ISO 8601 UTC", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--private-fields" },
        { "to": "/booking_url", "from": "url", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" },
        { "to": "/metadata/eventbrite_currency", "from": "currency", "transform": "ISO 4217 code of the event, kept for reconciliation", "source_url": "https://www.eventbrite.com/platform/api#ref-event--public-fields" }
      ]
    },
    "PriceTier": {
      "source": "GET /events/{event_id}/ticket_classes/ (Ticket Class)",
      "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--public-fields",
      "note": "Pointers are rooted at the parent (Offering or Event); /tiers/0 stands for each ticket class (GA, VIP…). Skip donation ticket classes and hidden ones. Money is Eventbrite's Currency object {currency: \"USD\", value: 432, major_value: \"4.32\", display: \"4.32 USD\"}, where value is already an integer in the currency's minor unit.",
      "fields": [
        { "to": "/tiers/0/external_id", "from": "id (the Ticket Class ID used as {ticket_class_id})", "transform": "prefix eb_tc_", "source_url": "https://www.eventbrite.com/platform/api#section-ref-ticket-class" },
        { "to": "/tiers/0/label", "from": "name", "transform": "truncate to 80 chars", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--public-fields" },
        { "to": "/tiers/0/amount", "from": "cost.value", "transform": "Currency.value is integer minor units (cents for USD): copy as is. Do not use major_value (decimal string) or display (locale-formatted). free = true → 0", "source_url": "https://www.eventbrite.com/platform/api#basic-types--currency" },
        { "to": "/tiers/0/currency", "from": "cost.currency", "transform": "ISO 4217 upper case → lower case (USD → usd)", "source_url": "https://www.eventbrite.com/platform/api#basic-types--currency" },
        { "to": "/tiers/0/unit", "from": "const:per_person", "transform": "one ticket admits one attendee (Attendee.quantity is always 1)", "source_url": "https://www.eventbrite.com/platform/api#ref-attendee--attendee-fields" },
        { "to": "/tiers/0/min_party", "from": "minimum_quantity", "transform": "minimum tickets per order; clamp to 1..50", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--public-fields" },
        { "to": "/tiers/0/max_party", "from": "maximum_quantity", "transform": "maximum tickets per order; clamp to 1..50", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--public-fields" },
        { "to": "/tiers/0/fees/0", "from": "fee", "transform": "{type: service, label: \"Eventbrite fee\", amount: fee.value, included: include_fee}. include_fee (private) = true means the fee is inside the shown price", "source_url": "https://www.eventbrite.com/platform/api#ref-ticket-class--private-fields" }
      ]
    },
    "Checkout": {
      "source": "GET /orders/{order_id}/?expand=attendees,event (the api_url of an Order webhook)",
      "source_url": "https://www.eventbrite.com/platform/api#ref-order--order-fields",
      "unverified": true,
      "note": "Reconciliation only. Pointers are rooted at checkout_create. Eventbrite's public API cannot create an order, so SplitClub cannot place one when a group checkout funds; this map is for a venue that records the group's order in Eventbrite itself (box office / comp) and wants the SplitClub checkout tied to it. Marked unverified because that flow is not something Eventbrite documents.",
      "fields": [
        { "to": "/external_booking_id", "from": "id (the Order ID used as {order_id})", "transform": "prefix eb_ord_", "source_url": "https://www.eventbrite.com/platform/api#section-ref-order" },
        { "to": "/amount", "from": "costs.gross.value", "transform": "Currency.value, integer minor units: copy as is", "source_url": "https://www.eventbrite.com/platform/api#order_cost" },
        { "to": "/currency", "from": "costs.gross.currency", "transform": "upper → lower case", "source_url": "https://www.eventbrite.com/platform/api#basic-types--currency" },
        { "to": "/party_size", "from": "attendees[] (expansion attendees)", "transform": "count of attendees (each Attendee.quantity is 1); must be 2..50", "source_url": "https://www.eventbrite.com/platform/api#ref-order--expansions" },
        { "to": "/offering_external_id", "from": "event_id", "transform": "prefix eb_evt_", "source_url": "https://www.eventbrite.com/platform/api#ref-order--order-fields" },
        { "to": "/description", "from": "event.name.text (expansion event)", "transform": "e.g. \"<event name>, <n> tickets\"", "source_url": "https://www.eventbrite.com/platform/api#ref-order--expansions" },
        { "to": "/pay_by", "from": "event.start.utc", "transform": "no Eventbrite field: SplitClub sets it, e.g. event start minus the venue's cutoff, never after the start", "source_url": "https://www.eventbrite.com/platform/api#basic-types--datetime-with-timezone" }
      ]
    }
  },
  "webhooks": {
    "supported": true,
    "events": [
      { "event": "Event: Publish", "maps_to": "GET api_url → upsert Event (and Offering)", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Event: Unpublish", "maps_to": "GET api_url → withdraw the Event from sale", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Event: Create", "maps_to": "ignore until published", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Event: Update", "maps_to": "GET api_url → upsert Event; status canceled → withdraw", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Order: Place", "maps_to": "GET api_url → reconcile against an open SplitClub Checkout (see Checkout)", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Order: Update", "maps_to": "GET api_url → reconcile", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Order: Refund", "maps_to": "GET api_url → flag the linked Checkout for review", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Attendee: Update / Check_in / Check_out", "maps_to": "ignore", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      { "event": "Organizer: Update", "maps_to": "ignore", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" },
      {
        "event": "Ticket Class: Create / Delete / Update",
        "maps_to": "GET api_url → upsert or remove the tier on its Event",
        "source_url": "https://www.eventbrite.com/platform/docs/webhooks"
      },
      { "event": "Venue: Update", "maps_to": "ignore", "source_url": "https://www.eventbrite.com/platform/docs/webhooks" }
    ],
    "note": "Every webhook body is {\"api_url\": \"https://www.eventbriteapi.com/v3/orders/1234567890/\", \"config\": {\"endpoint_url\": \"…\"}}, sent by POST as application/json: the object itself is not sent, so GET api_url with the venue's token. The guide names actions per object (Event: Publish, Unpublish, Create, Update; Order: Place, Update, Refund; Attendee: Update, Check_in, Check_out; Organizer: Update; Ticket Class: Create, Delete, Update; Venue: Update) and they are listed here exactly as written there. The dotted wire strings (e.g. order.placed, event.updated) and any header carrying them are NOT in the public docs, so do not route on them without seeing a real delivery. No signature scheme is documented: treat the body as untrusted, accept only api_url on https://www.eventbriteapi.com/v3/, and re-fetch with the venue's token (a forged hook then only causes a harmless re-read). Put a per-venue secret in the endpoint URL path. Non-2xx responses are retried every ten minutes, up to ten more attempts; a 410 unsubscribes the endpoint."
  },
  "gaps": [
    "No public endpoint creates an order or issues tickets, so a funded SplitClub checkout cannot become Eventbrite tickets by API. The venue must issue them (box office, comp, or a hidden ticket class / access code) and the Order webhook only reconciles. Needs a decision with the venue or Eventbrite's partner team.",
    "Webhook wire action names (order.placed etc.) and any request headers are not in the public docs; only the per-object action labels are. Confirm from a test delivery (the webhook detail page shows request headers and payload) before routing on them.",
    "No webhook signature is documented.",
    "Event and Ticket Class field tables do not list `id`; the ID is documented only as the {event_id} / {ticket_class_id} / {order_id} path parameter (it is present in responses).",
    "Order `status` is documented only as 'Order status.' with no value list in the field table.",
    "music_properties.age_restriction is an enum whose values the docs do not list, so policy.min_age is unverified.",
    "Event `capacity`, ticket class `capacity`, `quantity_sold`, `sales_start`/`sales_end` and `include_fee` are private fields: they need the organization's own OAuth token, not a public key.",
    "Remaining tickets per class (capacity − quantity_sold) could feed Event capacity, but quantity_sold excludes tickets being bought in real time, so it lags.",
    "Reserved seating (Seat Map) and add-ons / merchandise are not mapped."
  ]
}
