VenuePilot Public API
Version: 1.0 Protocol: GraphQL over HTTPS
Overview
The VenuePilot Public API is a read-only GraphQL interface that exposes publicly-announced events and their associated venues, artists, and imagery. It is intended for third-party integrations that need to display upcoming events on external websites, calendars, mobile apps, aggregators, and partner platforms.
The API is intentionally narrow:
- Read-only. It contains no mutations; writes are handled by the authenticated internal APIs.
- Events-first. The single root query returns a paginated list of events. Venues, artists, and images are reached by traversing an event.
- Scoped by application. Every request is associated with an application, identified by an app id you receive from VenuePilot when your integration is provisioned.
Endpoint
POST https://public.api.venuepilot.com/graphqlAll requests are POST with a JSON body. The GraphQL endpoint follows the standard request shape:
json
{
"query": "query ExampleName { ... }",
"variables": { "key": "value" },
"operationName": "ExampleName"
}variables and operationName are optional.
Authentication
Every request must include your application id in the x-vp-app-id header. This is the only credential the API accepts; there are no per-user tokens or OAuth flows.
| Header | Required | Description |
|---|---|---|
x-vp-app-id | yes | Application id issued to your integration |
Content-Type | yes | Must be application/json |
App ids are 64-character lowercase hex strings issued out-of-band by VenuePilot. Treat them as secrets — anyone with the value can query the API as your integration.
Example:
http
POST /graphql HTTP/1.1
Host: public.api.venuepilot.com
Content-Type: application/json
x-vp-app-id: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
{"query":"{ events(filter:{accountIds:[42]}){ edges { node { id name } } } }"}Requests without a valid x-vp-app-id header are rejected at the controller layer with an HTTP 500 and a generic Rails error page — not a GraphQL JSON envelope. Treat any non-200 response from this endpoint as an authentication or transport failure and surface it accordingly. See Errors for details.
Request conventions
- Transport: HTTPS only. Plain HTTP is not supported.
- Method:
POST. - Body: JSON object with
query(required) and optionalvariablesandoperationName. - Responses: Always JSON. A successful response has a top-level
datakey; errors appear undererrorsper the GraphQL specification. - Compression: Responses are
gzip-compressed when the client sendsAccept-Encoding: gzip.
Introspection
The schema is fully introspectable. You can fetch the live schema at any time using a standard GraphQL introspection query — useful for code generation, schema diffing, or wiring up a typed client.
Introspection is not anonymous: the same x-vp-app-id header that gates regular queries is required for __schema / __type requests as well. Tools like GraphiQL, Apollo Codegen, or graphql-codegen must be configured to send the header.
bash
curl https://public.api.venuepilot.com/graphql \
-H 'Content-Type: application/json' \
-H "x-vp-app-id: $VP_APP_ID" \
-d '{"query":"{ __schema { queryType { name } types { name kind } } }"}'The full introspection query that most code generators issue is also accepted.
Schema at a glance
| Kind | Name |
|---|---|
| Root query | Query |
| Object types | Event, Venue, Artist, PublicArtistLink, Image, ImageVariant, EventConnection, EventEdge, PageInfo |
| Input types | EventSearchArguments |
| Scalars | Int, Float, String, Boolean, ISO8601DateTime |
There is a single root query field:
graphql
type Query {
events(
filter: EventSearchArguments!
first: Int
after: String
before: String
): EventConnection
}The API uses Relay-style cursor pagination. The last argument is not supported (see Pagination).
Root query: events
Returns a paginated list of publicly-announced, confirmed events that match the supplied filter.
Signature
graphql
events(
filter: EventSearchArguments!,
first: Int,
after: String,
before: String
): EventConnectionArguments
| Argument | Type | Required | Description |
|---|---|---|---|
filter | EventSearchArguments | yes | Criteria used to narrow the result set. See EventSearchArguments. |
first | Int | no | Number of events to return. Defaults to 30. Capped at 60. |
after | String | no | Cursor — return the page of events immediately after the one identified by this cursor. |
before | String | no | Cursor — return the page of events immediately before the one identified by this cursor. |
Pagination
The connection is Relay-style. A typical paging loop looks like this:
- Issue a first request with no
aftercursor and your desiredfirstpage size. - Read
pageInfo.endCursorfrom the response. - If
pageInfo.hasNextPageistrue, issue the next request withafter: <endCursor>. - Repeat until
hasNextPageisfalse.
Notes specific to this API:
firstpage size. Defaults to30, maximum60. Values above60are silently clamped.lastis not supported. Sendinglastwill result in a GraphQL execution error:`last` pagination is not supported. Usefirstwithafter/beforeinstead.hasPreviousPageis alwaysfalse. The connection only supports forward traversal semantics; do not rely onhasPreviousPageas a truthful backward indicator.- Cursor format. Cursors are opaque strings; clients must treat them as such. They are tied to the connection's internal sort order (
date,startTime,id) and should not be reused across different filters. - Stable ordering. Results are ordered by event date, then start time, then internal id. Two events on the same date and start time order by id ascending.
Result type
events returns an EventConnection. See EventConnection and EventEdge below.
Input types
EventSearchArguments
Filters applied to the events query. At least accountIds must be provided.
| Field | Type | Required | Description |
|---|---|---|---|
accountIds | [Int] | yes | Scope the search to these VenuePilot account ids. Pass [-1] to return events across all accounts on the platform. |
startDate | String | no | Lower bound on the event date, inclusive. Accepts YYYY-MM-DD. Defaults to no lower bound. |
endDate | String | no | Upper bound on the event date, inclusive. Accepts YYYY-MM-DD. Defaults to no upper bound. |
id | Int | no | Return only the event with this internal id. Mutually exclusive with slug; if both are given, id wins. |
slug | String | no | Return only the event with this slug. |
search | String | no | Free-text search across event name, artist name, and tags. |
lastUpdatedAt | ISO8601DateTime | no | Return events whose composite last-modified timestamp is strictly greater than this value. See note below. |
Notes:
- The filter is always applied on top of an implicit base query that already excludes draft, unconfirmed, and un-announced events. You cannot request events that have not been announced.
accountIds: [-1]is a platform-wide wildcard. Normal requests should enumerate the specific accounts you care about.startDate/endDateapply to the event date range as a whole. Multi-day events whose start or end falls inside the window — or which span the entire window — are also included.lastUpdatedAtdoes not compare against the field returned asupdatedAt. Server-side, the filter is evaluated against a composite column that takes the maximum ofupdated_atacross the event and several of its associations (artists, venue, etc.). This is intentional: it lets you incrementally sync changes that happen on a related record without missing them. The practical consequence is that you may receive events whose top-levelupdatedAtis older than the cursor you passed in; that is expected behavior.
Object types
Event
A publicly-announced event.
| Field | Type | Nullable | Description |
|---|---|---|---|
id | Int | no | Internal numeric id of the event. |
slug | String | no | URL-safe unique identifier. |
name | String | no | Display name of the event. |
startTime | ISO8601DateTime | yes | Scheduled start time, always in UTC. |
endTime | ISO8601DateTime | yes | Scheduled end time, always in UTC. |
doorTime | ISO8601DateTime | yes | Doors-open time, always in UTC. |
minimumAge | Int | yes | Minimum age required to attend. |
promoter | String | yes | Name of the promoter or organizer. |
support | String | yes | Free-text list of supporting acts. |
description | String | yes | Full event description / program copy. |
footerContent | String | yes | Supplementary copy shown in the event footer. |
ticketsUrl | String | yes | URL where tickets can be purchased. |
openIn | String | yes | How the tickets link should open. Currently always "new_tab". |
buttonText | String | yes | Custom label for the ticket purchase button. |
venue | Venue | yes | Venue at which the event takes place. |
artists | [Artist!] | yes | Artists performing at the event. |
images | [Image!] | yes | Cover images associated with the event. |
tags | [String!] | yes | Tags associated with the event. |
canceled | Boolean | yes | true if the event has been canceled. |
soldOut | Boolean | yes | true if the event is sold out. |
priceMin | Float | yes | Minimum ticket price, denominated in venue.currency. |
priceMax | Float | yes | Maximum ticket price, denominated in venue.currency. |
updatedAt | ISO8601DateTime | yes | Last modified timestamp of the event record. ISO 8601 with offset; not normalised to UTC. |
Notes:
startTime,endTime, anddoorTimeare normalised to UTC. They are always serialized as UTCISO8601DateTimevalues (...Z). To display them to a user, convert them usingvenue.timezone.updatedAtis not normalised to UTC. It is rendered with the server's local timezone offset. Compare it as a point in time, not as a string.- Currency.
priceMinandpriceMaxusevenue.currency; they are returned as plain floats with no currency metadata attached to the field itself. - Incremental sync. Use the
lastUpdatedAtfilter to fetch only events that have changed since your last poll. Note that this filter compares against a composite timestamp that is not the same as theupdatedAtfield returned on the event — see EventSearchArguments for the full explanation.
Venue
Where an event takes place.
| Field | Type | Nullable | Description |
|---|---|---|---|
id | Int | no | Internal numeric id of the venue. |
name | String | yes | Display name of the venue. |
street1 | String | yes | Primary street address line. |
street2 | String | yes | Secondary address line (suite, unit, floor). |
city | String | yes | City. |
state | String | yes | State, province, or region. |
postalCode | String | yes | Postal or ZIP code. |
country | String | yes | Country (ISO 3166 alpha-2 code when set). |
latitude | String | yes | Latitude coordinate as a decimal string. |
longitude | String | yes | Longitude coordinate as a decimal string. |
logoUrl | String | yes | URL of the venue logo. When the venue has no logo uploaded, a default VenuePilot logo URL is returned, so this field is rarely empty. |
locale | String | yes | Preferred locale (e.g. en, de). |
currency | String | yes | ISO 4217 currency code (e.g. usd, eur). |
timezone | String | yes | IANA timezone identifier (e.g. America/New_York). |
updatedAt | ISO8601DateTime | yes | Last modified timestamp of the venue record. ISO 8601 with offset; not normalised to UTC. |
Notes:
latitudeandlongitudeare strings, not floats, to preserve the exact precision supplied by venue operators.timezoneis the authoritative field for converting an event's UTC times into local venue time.
Artist
A performing artist attached to an event.
| Field | Type | Nullable | Description |
|---|---|---|---|
id | Int | yes | Internal numeric id of the artist. |
name | String | no | Display name of the artist. |
links | [PublicArtistLink!] | yes | External links associated with the artist. |
updatedAt | ISO8601DateTime | yes | Last modified timestamp of the artist record. |
PublicArtistLink
A single external link for an artist (website, social profile, etc.).
| Field | Type | Nullable | Description |
|---|---|---|---|
url | String | no | The URL of the artist's external profile or website. |
Image
An image associated with an event.
| Field | Type | Nullable | Description |
|---|---|---|---|
name | String | no | Descriptive name or label for the image. |
highlighted | Boolean | yes | true if the image should be emphasized in the UI. |
variants | [ImageVariant!] | yes | Pre-rendered variants of this image at different sizes. |
Notes:
- An
Imageis a logical asset; eachImageVariantis a concrete URL at a specific size. Variants without a source are omitted from the list.
ImageVariant
A concrete, sized version of an Image.
| Field | Type | Nullable | Description |
|---|---|---|---|
src | String | no | URL of the image for this variant. |
variant | String | no | Identifier for the variant, e.g. thumb, medium, large. |
EventConnection
Relay-style connection returned from the events query.
| Field | Type | Nullable | Description |
|---|---|---|---|
edges | [EventEdge] | yes | List of edges containing events and their cursors. |
nodes | [Event] | yes | Shortcut to the underlying event nodes. |
pageInfo | PageInfo | no | Pagination metadata for the current page. |
EventEdge
A single edge in an EventConnection.
| Field | Type | Nullable | Description |
|---|---|---|---|
node | Event | yes | The event associated with this edge. |
cursor | String | no | Opaque cursor identifying this edge in the current result set. |
PageInfo
Standard Relay pagination metadata.
| Field | Type | Nullable | Description |
|---|---|---|---|
hasNextPage | Boolean | no | true if another page of events exists after the current one. |
hasPreviousPage | Boolean | no | Always false for this API. Do not rely on this as a backward indicator. |
startCursor | String | yes | Cursor pointing at the first edge in the current page. |
endCursor | String | yes | Cursor pointing at the last edge in the current page. |
Scalars
| Scalar | Description |
|---|---|
Int | Signed 32-bit integer. |
Float | IEEE 754 double-precision floating point number. |
String | UTF-8 character sequence. |
Boolean | true or false. |
ISO8601DateTime | Date-time value serialized as an ISO 8601 string, e.g. 2026-04-10T19:30:00Z. Whether the value is normalised to UTC depends on the field — see the per-field notes. |
Examples
The examples below assume you have exported your app id as VP_APP_ID. They use curl for clarity; production integrations should use a proper GraphQL client.
1. Fetch the next 10 events for one account
graphql
query UpcomingEvents($filter: EventSearchArguments!) {
events(filter: $filter, first: 10) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
slug
name
startTime
venue {
name
city
timezone
currency
}
}
}
}
}Variables:
json
{ "filter": { "accountIds": [42] } }Request:
bash
curl https://public.api.venuepilot.com/graphql \
-H 'Content-Type: application/json' \
-H "x-vp-app-id: $VP_APP_ID" \
-d @- <<'JSON'
{
"query": "query UpcomingEvents($filter: EventSearchArguments!) { events(filter: $filter, first: 10) { pageInfo { hasNextPage endCursor } edges { cursor node { id slug name startTime venue { name city timezone currency } } } } }",
"variables": { "filter": { "accountIds": [42] } }
}
JSONResponse (trimmed; cursor and id values are illustrative):
json
{
"data": {
"events": {
"pageInfo": {
"hasNextPage": true,
"endCursor": "MjAyNi0wNC0xNXwyMDowMHwxMzU3MjE="
},
"edges": [
{
"cursor": "MjAyNi0wNC0xMHwxOTozMHwxMzU3MTY=",
"node": {
"id": 135716,
"slug": "headline-show-2026-04-10-grand-hall-9de2",
"name": "Headline Show",
"startTime": "2026-04-10T23:30:00Z",
"venue": {
"name": "Grand Hall",
"city": "New York",
"timezone": "America/New_York",
"currency": "usd"
}
}
}
]
}
}
}2. Filter by a date range and free-text search
graphql
query JazzInMay {
events(
filter: {
accountIds: [42]
startDate: "2026-05-01"
endDate: "2026-05-31"
search: "jazz"
}
first: 25
) {
edges {
node {
id
name
startTime
tags
}
}
}
}3. Incremental sync using lastUpdatedAt
Store the largest updatedAt you have seen and pass it back on the next poll:
graphql
query Changed($since: ISO8601DateTime!) {
events(
filter: { accountIds: [42], lastUpdatedAt: $since }
first: 60
) {
pageInfo { hasNextPage endCursor }
nodes {
id
slug
name
updatedAt
canceled
soldOut
priceMin
priceMax
}
}
}Variables:
json
{ "since": "2026-04-09T00:00:00Z" }Continue paginating with after: <endCursor> until hasNextPage is false, then persist the max updatedAt for the next cycle.
4. Look up one event by slug
graphql
query OneEvent {
events(filter: {
accountIds: [42]
slug: "headline-show-2026-05-14-generic-venue-ny-abcd"
}) {
nodes {
id
name
description
startTime
doorTime
minimumAge
ticketsUrl
buttonText
artists { name links { url } }
images {
name
highlighted
variants { variant src }
}
venue {
name
street1
city
state
postalCode
country
latitude
longitude
timezone
currency
}
}
}
}5. Paginate with after
graphql
query NextPage($filter: EventSearchArguments!, $after: String) {
events(filter: $filter, first: 30, after: $after) {
pageInfo { hasNextPage endCursor }
edges {
node { id name startTime }
}
}
}Variables for the second page:
json
{
"filter": { "accountIds": [42] },
"after": "MjAyNi0wNC0xNXwyMDowMHwxMzU3MjE="
}Errors
The API has two distinct error layers, and clients need to handle both.
1. Authentication / transport failures
If the request never reaches the GraphQL execution layer — most commonly because x-vp-app-id is missing or unrecognized — the controller responds with HTTP 500 and an HTML error page. There is no GraphQL errors envelope in this case. Clients should treat any non-200 response as a transport-level failure and not attempt to parse the body as JSON.
http
HTTP/2 500
content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>...We're sorry, but something went wrong...</html>Common causes:
x-vp-app-idheader missing.x-vp-app-idvalue not recognized as a provisioned application.- Other transport / infrastructure failures (DNS, TLS, gateway, etc.).
2. GraphQL execution errors
Once a request is authenticated and reaches the schema, errors follow the GraphQL specification. The HTTP status is 200 and the body is a JSON object with an errors array, optionally alongside a partial data payload:
json
{
"data": { "events": { "edges": null } },
"errors": [
{
"message": "`last` pagination is not supported",
"locations": [{ "line": 1, "column": 47 }],
"path": ["events", "edges"]
}
]
}Common cases:
| Situation | Message | How to recover |
|---|---|---|
Using last for pagination. | `last` pagination is not supported | Switch to first with after / before. |
| Filtering on an account that does not exist. | Empty result set (no error). | Confirm the account id is valid for your integration. |
| Malformed GraphQL query. | Standard GraphQL parse / validation error. | Fix the query document; use a schema-aware client. |
Retries
Network-level failures (connection refused, 5xx, timeouts) should be handled with the same retry policy you would apply to any other GraphQL service. The API is idempotent for reads, so safe retries with exponential backoff are appropriate. Do not retry on a 500 that turns out to be an authentication failure — fix the header instead.
Limits and gotchas
- Page size caps at 60. Requesting
first: 200will return 60 items without an error. - No mutations. The API is read-only. Writes go through the authenticated internal APIs.
- Only announced, confirmed events. Draft events, events in a non-confirmed state, and events that have been excluded from public listings are filtered out server-side. You cannot opt out of this filter.
- Events are the only root entry point. There is no standalone
venue(id:)orartist(id:)query; traverse from an event instead. hasPreviousPageis alwaysfalse. Do not use it to drive UI that walks backwards.- Cursors are tied to sort order. Cursors from one filter should not be reused in a request with a materially different filter.
- Prices are plain floats. There is no amount-in-minor-units field; use
venue.currencyto format for display. updatedAton the event lagslastUpdatedAt. Filtering bylastUpdatedAtmay return events whose top-levelupdatedAtis older than the cursor — see EventSearchArguments for the rationale.- Auth failures are HTTP 500, not GraphQL errors. Always check the HTTP status before parsing the body as JSON.
Changelog
| Version | Date | Notes |
|---|---|---|
| 1.0 | 2026-04-10 | Initial published version of this document. |