# Eventship API > The Eventship API lets you manage events, attendees, tickets, and more programmatically. Authenticate with your host API key. > Every error response carries its body under `detail`, in one of three shapes. Most give an object with a machine-readable `code` and a human-readable `message`, plus occasional extra fields. Some give a plain string. A request that fails schema validation gives an array of validation objects. Check the type of `detail` before reading `code`. > This file is generated from the OpenAPI specification at http://developer.eventship.com/openapi.json > and lists every endpoint in the public API. Interactive reference: http://developer.eventship.com/ ## Base URL `https://api.eventship.com` ## Authentication Your host API key. Create one from your host dashboard settings. ``` X-API-Key: es_live_your_key_here ``` ## Endpoints ### Events #### `GET /v1/events` List events for your host. Returns summary data for each event (title, dates, location, capacity). Use GET /v1/events/{event_slug} for full details including sessions, speakers, and ticket types — and, for hub events that accept community submissions, pass `includeCommunityEvents=true` there to also get the approved community-submitted schedule (`communityEvents`). | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `status` | query | string | optional | Filter by event status (draft, live, cancelled) | | `after` | query | string (date) | optional | Only events starting on or after this date (YYYY-MM-DD) | | `before` | query | string (date) | optional | Only events starting on or before this date (YYYY-MM-DD) | | `page` | query | integer (min 1, default 1) | optional | | | `pageSize` | query | integer (1 to 100, default 20) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].slug` | string | | | `data[].title` | string | | | `data[].description` | string | | | `data[].status` | string or null | | | `data[].image` | string or null | | | `data[].startDate` | string | | | `data[].endDate` | string or null | | | `data[].timezone` | string | | | `data[].venue` | string | | | `data[].address` | string | | | `data[].locationType` | string | | | `data[].virtualLink` | string or null | | | `data[].externalRegistrationUrl` | string or null | | | `data[].cityName` | string or null | | | `data[].stateName` | string or null | | | `data[].registeredCount` | integer | | | `data[].ticketsLeft` | integer or null | | | `data[].isSoldOut` | boolean or null | | | `data[].currency` | string | ISO 4217 code every price on this event is denominated in, including its ticket types. A client cannot assume USD, and cannot infer this from the host either: an imported event takes the currency the source page stated, an event created without one takes the host's Stripe settlement currency, and a currency Eventship cannot sell in (whether the source's or the host's) is substituted. A substitution keeps the numeric prices and changes the unit under them, so read this field rather than assuming. The create and import responses additionally name the replaced currency in `message`; the read endpoints have no such field and report only the resolved value here. | | `total` | integer | | | `page` | integer | | | `pageSize` | integer | | | `hasMore` | boolean | | #### `POST /v1/events` Create a new event. The host is automatically resolved from your API key. Provide an address to auto-geocode the location. By default the event is created as a **draft**. Pass ``publish=true`` to create and publish it as **live** in one step — ticket sales open immediately, default reminders are created, and the host receives a notification email. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `title` | body | string (1 to 500 chars) | required | | | `startDate` | body | string (date-time) | required | | | `description` | body | string | optional | | | `endDate` | body | string (date-time) | optional | When the event ends, in the event's timezone. Optional: omit it and the event is created lasting four hours from its start, which you can change at any time with a PATCH. | | `timezone` | body | string (default America/Los_Angeles) | optional | | | `locationType` | body | in_person \| virtual \| hybrid \| global (default in_person) | optional | | | `venue` | body | string | optional | | | `address` | body | string | optional | | | `virtualLink` | body | string | optional | | | `externalRegistrationUrl` | body | string | optional | | | `privateLocation` | body | boolean (default false) | optional | | | `isPublic` | body | boolean (default true) | optional | | | `showGuestList` | body | boolean (default true) | optional | | | `image` | body | string | optional | | | `bannerTemplate` | body | string | optional | | | `introductionsEnabled` | body | boolean (default false) | optional | | | `sendSaveTheDate` | body | boolean (default false) | optional | | | `feedbackEnabled` | body | boolean | optional | | | `enableAds` | body | boolean (default false) | optional | | | `publish` | body | boolean (default false) | optional | If true, the event is published immediately as live instead of remaining a draft. | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.slug` | string | | | `data.title` | string | | | `data.description` | string | | | `data.status` | string or null | | | `data.image` | string or null | | | `data.startDate` | string | | | `data.endDate` | string or null | | | `data.timezone` | string | | | `data.venue` | string | | | `data.address` | string | | | `data.locationType` | string | | | `data.virtualLink` | string or null | | | `data.externalRegistrationUrl` | string or null | | | `data.cityName` | string or null | | | `data.stateName` | string or null | | | `data.registeredCount` | integer | | | `data.ticketsLeft` | integer or null | | | `data.isSoldOut` | boolean or null | | | `data.currency` | string | ISO 4217 code every price on this event is denominated in, including its ticket types. A client cannot assume USD, and cannot infer this from the host either: an imported event takes the currency the source page stated, an event created without one takes the host's Stripe settlement currency, and a currency Eventship cannot sell in (whether the source's or the host's) is substituted. A substitution keeps the numeric prices and changes the unit under them, so read this field rather than assuming. The create and import responses additionally name the replaced currency in `message`; the read endpoints have no such field and report only the resolved value here. | | `data.ticketTypes` | array of object | | | `data.sessions` | array of object | | | `data.speakers` | array of object | | | `data.questions` | array of object | | | `data.hostSlug` | string or null | | | `data.stripeReady` | boolean | Whether the host has completed Stripe onboarding (needed for paid tickets). | | `data.communityEvents` | array of object or null | Approved community-submitted events on the hub's schedule, ordered by start time. Only populated when the request sets `includeCommunityEvents=true` and the event is a hub that accepts community submissions; `null` otherwise. | | `success` | boolean | | | `message` | string or null | | #### `POST /v1/events/import` Import an event from an external URL. Accepts a URL to an event page (Eventbrite, Luma, Meetup, etc.), extracts the event data, and creates a draft event on Eventship. Ticket types, sessions, and location data are imported automatically. The event is priced in the currency the source stated, or in your host's own currency when it stated none. If the source used a currency Eventship cannot sell in, the imported prices keep their numbers and the event is created in your currency instead: `message` then says which currency was substituted. Check it before publishing a priced event. A source page that states no end time produces an event ending four hours after its start, which you can change at any time with a PATCH. The page's agenda does not extend it. The import refuses a page whose START date it cannot read. It also refuses several other things, so do not treat that as the only failure: a missing title, tickets it could parse but not price, tickets in mixed currencies, and structurally invalid tickets each return their own error code. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `url` | body | string | required | URL of the external event page to import | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.slug` | string | | | `data.title` | string | | | `data.description` | string | | | `data.status` | string or null | | | `data.image` | string or null | | | `data.startDate` | string | | | `data.endDate` | string or null | | | `data.timezone` | string | | | `data.venue` | string | | | `data.address` | string | | | `data.locationType` | string | | | `data.virtualLink` | string or null | | | `data.externalRegistrationUrl` | string or null | | | `data.cityName` | string or null | | | `data.stateName` | string or null | | | `data.registeredCount` | integer | | | `data.ticketsLeft` | integer or null | | | `data.isSoldOut` | boolean or null | | | `data.currency` | string | ISO 4217 code every price on this event is denominated in, including its ticket types. A client cannot assume USD, and cannot infer this from the host either: an imported event takes the currency the source page stated, an event created without one takes the host's Stripe settlement currency, and a currency Eventship cannot sell in (whether the source's or the host's) is substituted. A substitution keeps the numeric prices and changes the unit under them, so read this field rather than assuming. The create and import responses additionally name the replaced currency in `message`; the read endpoints have no such field and report only the resolved value here. | | `data.ticketTypes` | array of object | | | `data.sessions` | array of object | | | `data.speakers` | array of object | | | `data.questions` | array of object | | | `data.hostSlug` | string or null | | | `data.stripeReady` | boolean | Whether the host has completed Stripe onboarding (needed for paid tickets). | | `data.communityEvents` | array of object or null | Approved community-submitted events on the hub's schedule, ordered by start time. Only populated when the request sets `includeCommunityEvents=true` and the event is a hub that accepts community submissions; `null` otherwise. | | `success` | boolean | | | `message` | string or null | | #### `GET /v1/events/{event_slug}` Get event details by slug. Hub events (events that accept community submissions) can also embed their approved community-submitted schedule: pass `includeCommunityEvents=true` and the response includes a `communityEvents` array ordered by start time. The field is `null` unless requested and the event is a hub. Only approved, live, public, listed events appear — the same privacy rules as the hub's public schedule page. Each community event carries its own `description` and, when its host takes registrations elsewhere, `externalRegistrationUrl` — enough to mirror the approved schedule onto your own site without a second call per event. Those events belong to other hosts, so their descriptions are user-authored content from outside your account: sanitize before rendering as HTML. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `includeCommunityEvents` | query | boolean (default false) | optional | Set true to also return the hub's approved community-submitted events as `communityEvents` (hubs only — events that accept community submissions). Off by default to keep the payload lean. | Returns: | Name | Type | Description | | --- | --- | --- | | `slug` | string | | | `title` | string | | | `description` | string | | | `status` | string or null | | | `image` | string or null | | | `startDate` | string | | | `endDate` | string or null | | | `timezone` | string | | | `venue` | string | | | `address` | string | | | `locationType` | string | | | `virtualLink` | string or null | | | `externalRegistrationUrl` | string or null | | | `cityName` | string or null | | | `stateName` | string or null | | | `registeredCount` | integer | | | `ticketsLeft` | integer or null | | | `isSoldOut` | boolean or null | | | `currency` | string | ISO 4217 code every price on this event is denominated in, including its ticket types. A client cannot assume USD, and cannot infer this from the host either: an imported event takes the currency the source page stated, an event created without one takes the host's Stripe settlement currency, and a currency Eventship cannot sell in (whether the source's or the host's) is substituted. A substitution keeps the numeric prices and changes the unit under them, so read this field rather than assuming. The create and import responses additionally name the replaced currency in `message`; the read endpoints have no such field and report only the resolved value here. | | `ticketTypes` | array of object | | | `ticketTypes[].id` | integer | | | `ticketTypes[].name` | string | | | `ticketTypes[].description` | string | | | `ticketTypes[].price` | number | | | `ticketTypes[].quantity` | integer or null | | | `ticketTypes[].maxPerOrder` | integer | | | `ticketTypes[].soldCount` | integer | | | `ticketTypes[].availableCount` | integer or null | | | `ticketTypes[].isSoldOut` | boolean | | | `ticketTypes[].absorbFees` | boolean | | | `ticketTypes[].salesStartAt` | string (date-time) or null | | | `ticketTypes[].salesEndAt` | string (date-time) or null | | | `sessions` | array of object | | | `sessions[].id` | integer | | | `sessions[].title` | string | | | `sessions[].description` | string | | | `sessions[].startTime` | string (date-time) or null | | | `sessions[].endTime` | string (date-time) or null | | | `sessions[].location` | string | | | `sessions[].venue` | string | | | `sessions[].room` | string or null | | | `sessions[].address` | string or null | | | `sessions[].latitude` | number or null | | | `sessions[].longitude` | number or null | | | `sessions[].googlePlaceId` | string or null | | | `sessions[].venueId` | string or null | | | `sessions[].image` | string or null | Host-uploaded session image (16:9 preferred), or null when the host has not set one. Sessions do not inherit the event image, so a client wanting a guaranteed thumbnail should fall back to the event's `image` itself. | | `sessions[].featured` | boolean | Whether the host has spotlighted this session at the top of the public agenda. Eventship renders a featured session as a large card backed by `image`, and an unfeatured one as a row with `image` as a thumbnail, so the two fields are read together. | | `sessions[].speakers` | array of object | | | `speakers` | array of object | | | `speakers[].name` | string | | | `speakers[].title` | string or null | | | `speakers[].company` | string or null | | | `speakers[].headshot` | string or null | | | `speakers[].bio` | string or null | | | `speakers[].role` | string or null | | | `questions` | array of object | | | `questions[].id` | integer | | | `questions[].questionText` | string | | | `questions[].questionType` | text \| textarea \| select \| checkbox \| multiselect \| address | | | `questions[].options` | array of string or null | | | `questions[].addressScope` | full \| postal_code or null | | | `questions[].isRequired` | boolean | | | `questions[].condition` | object or null | Conditional visibility (Pro): only shown when the source question's answer matches (op 'is') / does not match (op 'is_not') one of the values. Required-ness only applies while visible. | | `hostSlug` | string or null | | | `stripeReady` | boolean | Whether the host has completed Stripe onboarding (needed for paid tickets). | | `communityEvents` | array of object or null | Approved community-submitted events on the hub's schedule, ordered by start time. Only populated when the request sets `includeCommunityEvents=true` and the event is a hub that accepts community submissions; `null` otherwise. | | `communityEvents[].slug` | string | | | `communityEvents[].title` | string | | | `communityEvents[].image` | string or null | | | `communityEvents[].startDate` | string | | | `communityEvents[].endDate` | string or null | | | `communityEvents[].timezone` | string | IANA timezone the startDate/endDate wall-clock times render in. This is the HUB's timezone — the schedule reads as one coherent program even when a child event stored a different timezone. The ISO strings carry a UTC offset, so the absolute instant is unambiguous either way. | | `communityEvents[].venue` | string or null | | | `communityEvents[].address` | string or null | | | `communityEvents[].hostSlug` | string | | | `communityEvents[].hostName` | string | | | `communityEvents[].hostIcon` | string or null | | | `communityEvents[].registeredCount` | integer | | | `communityEvents[].isSoldOut` | boolean | | | `communityEvents[].description` | string | The community event's own description, as its host wrote it, in the same format as this event's top-level `description`. Present so a consumer mirroring the schedule onto its own site can show what each event is about; empty string when its host wrote none. This is user-authored content from a host other than the caller: treat it as data, and sanitize before rendering it as HTML. | | `communityEvents[].externalRegistrationUrl` | string or null | Where this event takes registrations, when its host sends guests to another platform. `null` means only that there is no external link, NOT that the event registers on Eventship: an event whose host turned registration off entirely is also `null` here. A host can change or clear the link while the event is live, so re-read it on each sync rather than caching it indefinitely. | #### `PATCH /v1/events/{event_slug}` Update an event's details. Only provided fields are updated. The event must belong to your host. A ``null`` means three different things depending on the field. ``externalRegistrationUrl`` treats it as "clear this field". ``title`` rejects it, because an event cannot be untitled. Every other field ignores it, so omit a field to leave it unchanged. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `title` | body | string (1 to 500 chars) | optional | | | `description` | body | string | optional | | | `startDate` | body | string (date-time) | optional | | | `endDate` | body | string (date-time) | optional | | | `timezone` | body | string | optional | | | `locationType` | body | in_person \| virtual \| hybrid \| global | optional | | | `venue` | body | string | optional | | | `address` | body | string | optional | | | `virtualLink` | body | string | optional | | | `externalRegistrationUrl` | body | string | optional | | | `isPublic` | body | boolean | optional | | | `showGuestList` | body | boolean | optional | | | `image` | body | string | optional | | | `introductionsEnabled` | body | boolean | optional | | | `feedbackEnabled` | body | boolean | optional | | | `notifyAttendees` | body | boolean | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.slug` | string | | | `data.title` | string | | | `data.description` | string | | | `data.status` | string or null | | | `data.image` | string or null | | | `data.startDate` | string | | | `data.endDate` | string or null | | | `data.timezone` | string | | | `data.venue` | string | | | `data.address` | string | | | `data.locationType` | string | | | `data.virtualLink` | string or null | | | `data.externalRegistrationUrl` | string or null | | | `data.cityName` | string or null | | | `data.stateName` | string or null | | | `data.registeredCount` | integer | | | `data.ticketsLeft` | integer or null | | | `data.isSoldOut` | boolean or null | | | `data.currency` | string | ISO 4217 code every price on this event is denominated in, including its ticket types. A client cannot assume USD, and cannot infer this from the host either: an imported event takes the currency the source page stated, an event created without one takes the host's Stripe settlement currency, and a currency Eventship cannot sell in (whether the source's or the host's) is substituted. A substitution keeps the numeric prices and changes the unit under them, so read this field rather than assuming. The create and import responses additionally name the replaced currency in `message`; the read endpoints have no such field and report only the resolved value here. | | `data.ticketTypes` | array of object | | | `data.sessions` | array of object | | | `data.speakers` | array of object | | | `data.questions` | array of object | | | `data.hostSlug` | string or null | | | `data.stripeReady` | boolean | Whether the host has completed Stripe onboarding (needed for paid tickets). | | `data.communityEvents` | array of object or null | Approved community-submitted events on the hub's schedule, ordered by start time. Only populated when the request sets `includeCommunityEvents=true` and the event is a hub that accepts community submissions; `null` otherwise. | | `success` | boolean | | | `message` | string or null | | ### Attendees #### `GET /v1/events/{event_slug}/attendees` List attendees for an event. Access rules: - Hosts of the event see the full attendee list (name, email, status, ticket type, check-in status, registration date). The ``status`` filter applies, and ``includeAnswers=true`` adds each registrant's registration-question answers. - Confirmed registered attendees see a filtered list (name + public profile info only) when the event has ``showGuestList=true``. Host-only fields are ``null``. Always limited to confirmed registrations; the ``status`` filter is ignored, and ``includeAnswers`` is refused. - API-key auth is always host-only (API keys are scoped to a host). | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `status` | query | string | optional | Filter by registration status (host only — ignored for non-host callers) | | `includeAnswers` | query | boolean (default false) | optional | Include each registrant's answers to the event's registration questions (host only). Off by default so existing integrations keep their current payload size. | | `page` | query | integer (min 1, default 1) | optional | | | `pageSize` | query | integer (1 to 200, default 50) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].name` | string | | | `data[].profileSlug` | string or null | | | `data[].companyName` | string or null | | | `data[].email` | string or null | | | `data[].status` | string or null | | | `data[].ticketType` | string or null | | | `data[].checkedIn` | boolean or null | | | `data[].registeredAt` | string (date-time) or null | | | `data[].answers` | array of object or null | This registrant's answers to the event's registration questions, in the questions' display order. Host view only, and only when the request sets `includeAnswers=true`; `null` otherwise. An answer appears once the registrant submitted it, whether the question is one of yours or one of Eventship's built-in profile questions. | | `total` | integer | | | `page` | integer | | | `pageSize` | integer | | | `hasMore` | boolean | | ### Ticket Types #### `GET /v1/events/{event_slug}/ticket-types` List ticket types for an event. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | integer | | | `data[].name` | string | | | `data[].description` | string | | | `data[].price` | number | | | `data[].quantity` | integer or null | | | `data[].maxPerOrder` | integer | | | `data[].soldCount` | integer | | | `data[].availableCount` | integer or null | | | `data[].isSoldOut` | boolean | | | `data[].absorbFees` | boolean | | | `data[].salesStartAt` | string (date-time) or null | | | `data[].salesEndAt` | string (date-time) or null | | | `success` | boolean | | | `message` | string or null | | #### `POST /v1/events/{event_slug}/ticket-types` Create a new ticket type for an event. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `name` | body | string | required | | | `description` | body | string | optional | | | `price` | body | number (min 0, default 0) | optional | | | `quantity` | body | integer (min 1) | optional | | | `maxPerOrder` | body | integer (1 to 99, default 20) | optional | | | `absorbFees` | body | boolean (default false) | optional | | | `salesStartAt` | body | string (date-time) | optional | | | `salesEndAt` | body | string (date-time) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | integer | | | `data.name` | string | | | `data.description` | string | | | `data.price` | number | | | `data.quantity` | integer or null | | | `data.maxPerOrder` | integer | | | `data.soldCount` | integer | | | `data.availableCount` | integer or null | | | `data.isSoldOut` | boolean | | | `data.absorbFees` | boolean | | | `data.salesStartAt` | string (date-time) or null | | | `data.salesEndAt` | string (date-time) or null | | | `success` | boolean | | | `message` | string or null | | #### `PATCH /v1/events/{event_slug}/ticket-types/{ticket_type_id}` Update a ticket type. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `ticket_type_id` | path | integer | required | | | `name` | body | string | optional | | | `description` | body | string | optional | | | `price` | body | number (min 0) | optional | | | `quantity` | body | integer (min 1) | optional | | | `maxPerOrder` | body | integer (1 to 99, default 20) | optional | | | `absorbFees` | body | boolean | optional | | | `salesStartAt` | body | string (date-time) | optional | | | `salesEndAt` | body | string (date-time) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | integer | | | `data.name` | string | | | `data.description` | string | | | `data.price` | number | | | `data.quantity` | integer or null | | | `data.maxPerOrder` | integer | | | `data.soldCount` | integer | | | `data.availableCount` | integer or null | | | `data.isSoldOut` | boolean | | | `data.absorbFees` | boolean | | | `data.salesStartAt` | string (date-time) or null | | | `data.salesEndAt` | string (date-time) or null | | | `success` | boolean | | | `message` | string or null | | #### `DELETE /v1/events/{event_slug}/ticket-types/{ticket_type_id}` Delete a ticket type. Refused while anyone is still registered on the tier, including people awaiting approval and people on the waitlist, and refused when the tier has paid registrations on record even after they were cancelled or declined. Set an end date on the tier's sales to stop new signups instead. Refused temporarily, with 409 TICKET_TYPE_CHECKOUT_IN_FLIGHT, while a checkout is holding the tier. That one clears on its own: retry shortly rather than treating it as permanent. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `ticket_type_id` | path | integer | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `success` | boolean | | | `message` | string or null | | ### Speakers #### `GET /v1/events/{event_slug}/speakers` List the speakers on an event. Each entry's ``id`` is the appearance id used to update or remove that speaker, ``speakerId`` is the shared profile id, ``sessionIds`` are the sessions they speak at, and ``createdByMe`` indicates whether you created the profile (only then can you edit its canonical name). | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].speakerId` | string | | | `data[].name` | string | | | `data[].title` | string or null | | | `data[].company` | string or null | | | `data[].headshot` | string or null | | | `data[].bio` | string or null | | | `data[].linkedin` | string or null | | | `data[].role` | string or null | | | `data[].sessionIds` | array of integer | | | `data[].sortOrder` | integer | | | `data[].createdByMe` | boolean | | | `success` | boolean | | | `message` | string or null | | #### `POST /v1/events/{event_slug}/speakers` Add a speaker to an event. Provide ``speakerId`` (from ``GET /v1/speakers/search``) to reuse an existing profile, or ``name`` (plus optional ``title``/``company``/``bio``/ ``headshot``/``linkedin``) to create a new one. When reusing a profile, any fields you send backfill the shared profile if it was empty, otherwise apply only to this event. **Request body: required JSON object.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `speakerId` | body | string (uuid) | optional | | | `name` | body | string (max 255 chars) | optional | | | `title` | body | string (max 255 chars) | optional | | | `company` | body | string (max 255 chars) | optional | | | `companyId` | body | string (uuid) | optional | | | `bio` | body | string | optional | | | `headshot` | body | string (max 2048 chars) | optional | | | `linkedin` | body | string (max 2048 chars) | optional | | | `sessionIds` | body | array of integer | optional | | | `role` | body | string (max 100 chars) | optional | | | `customTitle` | body | string (max 255 chars) | optional | | | `customBio` | body | string | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.speakerId` | string | | | `data.name` | string | | | `data.title` | string or null | | | `data.company` | string or null | | | `data.headshot` | string or null | | | `data.bio` | string or null | | | `data.linkedin` | string or null | | | `data.role` | string or null | | | `data.sessionIds` | array of integer | | | `data.sortOrder` | integer | | | `data.createdByMe` | boolean | | | `success` | boolean | | | `message` | string or null | | #### `PATCH /v1/events/{event_slug}/speakers/{appearance_id}` Update a speaker on an event. ``appearance_id`` is the ``id`` from ``GET /v1/events/{event_slug}/speakers``. Send ``""`` to clear a field for this event; omit a field to leave it unchanged. The canonical ``name`` changes only if you created the profile. **Request body: required JSON object.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `appearance_id` | path | string | required | | | `sessionIds` | body | array of integer | optional | | | `role` | body | string (max 100 chars) | optional | | | `customTitle` | body | string (max 255 chars) | optional | | | `customBio` | body | string | optional | | | `name` | body | string (max 255 chars) | optional | | | `title` | body | string (max 255 chars) | optional | | | `company` | body | string (max 255 chars) | optional | | | `headshot` | body | string (max 2048 chars) | optional | | | `bio` | body | string | optional | | | `linkedin` | body | string (max 2048 chars) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.speakerId` | string | | | `data.name` | string | | | `data.title` | string or null | | | `data.company` | string or null | | | `data.headshot` | string or null | | | `data.bio` | string or null | | | `data.linkedin` | string or null | | | `data.role` | string or null | | | `data.sessionIds` | array of integer | | | `data.sortOrder` | integer | | | `data.createdByMe` | boolean | | | `success` | boolean | | | `message` | string or null | | #### `DELETE /v1/events/{event_slug}/speakers/{appearance_id}` Remove a speaker from an event. Deletes the appearance (all sessions for that speaker on this event); the shared speaker profile itself is never deleted. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `appearance_id` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `success` | boolean | | | `message` | string or null | | #### `GET /v1/speakers/search` Search the shared speaker directory by name. Speaker profiles are reusable across events, so call this first and reuse a match (pass its ``id`` as ``speakerId`` when adding) instead of creating a duplicate. This is the same search-first flow the web editor uses. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `q` | query | string (1 to 100 chars) | required | Search query | | `limit` | query | integer (1 to 50, default 10) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].slug` | string | | | `data[].name` | string | | | `data[].title` | string or null | | | `data[].companyName` | string or null | | | `data[].headshot` | string or null | | | `success` | boolean | | | `message` | string or null | | ### Questions #### `GET /v1/events/{event_slug}/question-insights` AI-summarized aggregate of confirmed attendees' registration answers. Returns one entry per question worth aggregating (e.g. dietary preference, the city people are travelling from), each with bucketed counts across the going attendees. Pro feature. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `insights` | array of object | | | `insights[].label` | string | | | `insights[].buckets` | array of object | | | `insights[].moreCount` | integer | Number of distinct answer values beyond the returned buckets (buckets are capped at the most common few). | #### `GET /v1/events/{event_slug}/questions` List registration questions for an event. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `[].id` | integer | | | `[].questionText` | string | | | `[].questionType` | text \| textarea \| select \| checkbox \| multiselect \| address | | | `[].options` | array of string or null | | | `[].addressScope` | full \| postal_code or null | | | `[].isRequired` | boolean | | | `[].condition` | object or null | Conditional visibility (Pro): only shown when the source question's answer matches (op 'is') / does not match (op 'is_not') one of the values. Required-ness only applies while visible. | | `[].condition.sourceQuestionId` | integer | | | `[].condition.op` | is \| is_not | | | `[].condition.values` | array of string | | #### `POST /v1/events/{event_slug}/questions` Add a registration question to an event. ``condition`` (Pro) makes the question conditional on an earlier select / multiselect / checkbox question's answer. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `questionText` | body | string (max 2000 chars) | required | | | `questionType` | body | text \| textarea \| select \| checkbox \| multiselect (default text) | optional | | | `options` | body | array of string | optional | | | `isRequired` | body | boolean (default false) | optional | | | `condition` | body | object | optional | Conditional visibility (Pro plan). The source question must be an earlier select / multiselect / checkbox question; values must be among its options ('yes'/'no' for checkbox). | | `condition.sourceQuestionId` | body | integer | required | | | `condition.op` | body | is \| is_not | required | | | `condition.values` | body | array of string (min 1 items) | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `id` | integer | | | `questionText` | string | | | `questionType` | text \| textarea \| select \| checkbox \| multiselect \| address | | | `options` | array of string or null | | | `addressScope` | full \| postal_code or null | | | `isRequired` | boolean | | | `condition` | object or null | Conditional visibility (Pro): only shown when the source question's answer matches (op 'is') / does not match (op 'is_not') one of the values. Required-ness only applies while visible. | | `condition.sourceQuestionId` | integer | | | `condition.op` | is \| is_not | | | `condition.values` | array of string | | #### `PATCH /v1/events/{event_slug}/questions/{question_id}` Update a registration question. ``condition`` is tri-state: omitted = unchanged, null = remove logic, object = set logic (Pro). **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `question_id` | path | integer | required | | | `questionText` | body | string (max 2000 chars) | optional | | | `questionType` | body | text \| textarea \| select \| checkbox \| multiselect | optional | | | `options` | body | array of string | optional | | | `isRequired` | body | boolean | optional | | | `condition` | body | object | optional | | | `condition.sourceQuestionId` | body | integer | required | | | `condition.op` | body | is \| is_not | required | | | `condition.values` | body | array of string (min 1 items) | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `id` | integer | | | `questionText` | string | | | `questionType` | text \| textarea \| select \| checkbox \| multiselect \| address | | | `options` | array of string or null | | | `addressScope` | full \| postal_code or null | | | `isRequired` | boolean | | | `condition` | object or null | Conditional visibility (Pro): only shown when the source question's answer matches (op 'is') / does not match (op 'is_not') one of the values. Required-ness only applies while visible. | | `condition.sourceQuestionId` | integer | | | `condition.op` | is \| is_not | | | `condition.values` | array of string | | #### `DELETE /v1/events/{event_slug}/questions/{question_id}` Remove a registration question from an event. Conditions on other questions that depend on the deleted one are cleared — they become always-visible. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `question_id` | path | integer | required | | ### Registration #### `POST /v1/events/{event_slug}/register` Register an attendee for a free event. Only free tickets are supported via the API. For paid events, direct attendees to the event page on eventship.com. Ticket types that sell in packs are not supported either, and are refused with ``PACK_TICKET_UNSUPPORTED``: this endpoint creates a single registration, while one pack unit is worth several seats. That applies both when ``ticketTypeId`` names a pack and when auto-selection finds only packs. If the event has required registration questions, call ``GET /v1/events/{event_slug}/questions`` first to get them, then include answers in this request. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `email` | body | string | required | Attendee email address (required) | | `firstName` | body | string | optional | | | `lastName` | body | string | optional | | | `ticketTypeId` | body | integer | optional | | | `answers` | body | array of object | optional | | | `answers[].questionId` | body | integer | required | | | `answers[].answerText` | body | string | optional | | | `answers[].answerJson` | body | array of string | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `status` | string | | | `registrationId` | string | | | `email` | string | | | `ticketType` | string or null | | ### Hubs #### `GET /v1/hubs` List your host's hubs: the events that carry a community agenda. Use this to find the `hubSlug` the other hub endpoints take. Most recent first, so the hub a host is actually curating is on the first page. Page past `limit` with `offset` when `hasMore` is true. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer (1 to 100, default 50) | optional | | | `offset` | query | integer (min 0, default 0) | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.hubs` | array of object | | | `data.total` | integer | Total hubs on the host, which may exceed the number returned. Page through them with `offset` while `hasMore` is true. | | `data.hasMore` | boolean | True when hubs remain past this page. Request the next page with `offset` raised by `limit`. | | `success` | boolean | | | `message` | string or null | | #### `GET /v1/hubs/{hub_slug}/events` Get the events currently on a hub's agenda. Returns the approved, publicly visible child events ordered by start time, with times rendered in the hub's timezone so the schedule reads as one program. A child that was added and has since been unpublished, made private or deleted does not appear. Each event carries its own `description` and, when its host takes registrations elsewhere, `externalRegistrationUrl`, so a site mirroring the agenda has everything it needs from this one call. The events belong to other hosts, so treat their descriptions as user-authored content and sanitize before rendering as HTML. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `hub_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.hubSlug` | string | | | `data.hubTitle` | string | | | `data.events` | array of object | | | `data.total` | integer | | | `success` | boolean | | | `message` | string or null | | #### `POST /v1/hubs/{hub_slug}/events` Put a live event on your hub's agenda. The event may belong to any host. It must be live, public and not unlisted, and it cannot itself be a hub. Adding is immediate, with no review step, and the added event's hosts are emailed that a hub picked their event up. The hub's own `status`, `isPublic` and `unlisted` decide whether the hub is LISTED publicly, not whether its agenda can be read. A live private or unlisted hub is link-only, and its schedule is readable by anyone holding that link, so adding is visible immediately rather than staged. Only a draft hub is unreachable from its own page, and hubs are curatable before they go live on purpose, so adding to one is not an error. `GET /v1/hubs` reports all three fields (`status`, `isPublic`, `unlisted`). The notification runs after the change is committed and swallows delivery failures, so it is not a guarantee that the added event's hosts learn of the change before the public can see it. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `hub_slug` | path | string | required | | | `eventSlug` | body | string (min 1 chars) | required | URL slug of the event to add. It may belong to any host, not just yours, but it must be live, public and not unlisted. | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.hubSlug` | string | | | `data.hubTitle` | string | | | `data.eventSlug` | string | | | `data.eventTitle` | string | | | `data.status` | string | Agenda state of the event on this hub: "approved" means it is on the agenda, "rejected" means it has been removed. | | `success` | boolean | | | `message` | string or null | | #### `DELETE /v1/hubs/{hub_slug}/events/{child_event_slug}` Take an event off your hub's agenda. Same action as "Remove from agenda" in the dashboard: the submission is marked rejected so the hub host keeps a record of what was removed, and the event stops appearing on the hub's schedule. The event itself is untouched. The path parameter is named ``child_event_slug`` rather than ``event_slug`` on purpose: OAuth host resolution derives the acting host from an ``event_slug`` in the path, and here that slug belongs to somebody else's event. ``hub_slug`` is the one that identifies the caller's host. | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `hub_slug` | path | string | required | | | `child_event_slug` | path | string | required | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.hubSlug` | string | | | `data.hubTitle` | string | | | `data.eventSlug` | string | | | `data.eventTitle` | string | | | `data.status` | string | Agenda state of the event on this hub: "approved" means it is on the agenda, "rejected" means it has been removed. | | `success` | boolean | | | `message` | string or null | | ### Broadcast #### `POST /v1/events/{event_slug}/broadcast` Send a message to attendees of an event. Supports the same filtering options as the web app's broadcast composer: - Filter by registration status (``confirmed``, ``pending``, ``invited``, ``waitlisted``, ``cancelled``, ``declined``). Omit to target the default set (confirmed + pending + invited). - Filter by specific ticket type IDs. Omit to include all ticket types. - Optionally post the message to the event feed. Recipients who have opted out of event messages for this host are automatically excluded. Free-tier hosts are subject to the monthly email quota; upgrade to Pro for a higher allowance. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `subject` | body | string (max 500 chars) | optional | Email subject line. Optional — if omitted or blank, defaults to "📣 {event title} update" (the same placeholder the web composer pre-fills for hosts). | | `bodyHtml` | body | string (min 1 chars) | required | Email body. Plain text or safe HTML. | | `registrationStatuses` | body | array of string | optional | Filter by registration status. Valid values: 'confirmed', 'pending', 'invited', 'waitlisted', 'cancelled', 'declined'. Omit to target the default set (confirmed, pending, invited). | | `ticketTypeIds` | body | array of integer | optional | Filter by ticket type IDs. Omit to include all ticket types. | | `postToFeed` | body | boolean (default false) | optional | If true, also post the message to the event's feed. | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.broadcastId` | string | | | `data.recipientsCount` | integer | | | `success` | boolean | | | `message` | string or null | | #### `POST /v1/events/{event_slug}/broadcast/count` Preview how many recipients a broadcast would reach with the given filters. Useful for verifying targeting before sending. Applies the same opt-out and email-presence filters as the send endpoint. **Request body: required JSON object. Unknown fields are rejected.** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `event_slug` | path | string | required | | | `registrationStatuses` | body | array of string | optional | | | `ticketTypeIds` | body | array of integer | optional | | Returns: | Name | Type | Description | | --- | --- | --- | | `data` | object | | | `data.recipientsCount` | integer | | | `success` | boolean | | | `message` | string or null | |