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 page lists every public endpoint. The interactive reference needs JavaScript. The same reference in plain text: llms.txt.
Base URL
https://api.eventship.comAuthentication
Your host API key. Create one from your host dashboard settings.
X-API-Key: es_live_your_key_hereEndpoints
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 |
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. |
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 |
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. |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
GET /v1/events/{event_slug}/questions
List registration questions for an event.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
event_slug | path | string | required |
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 |
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 |
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 |
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 |
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 |
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. |
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 |
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. |
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 |