Skip to content

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/graphql

All 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.

HeaderRequiredDescription
x-vp-app-idyesApplication id issued to your integration
Content-TypeyesMust 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 optional variables and operationName.
  • Responses: Always JSON. A successful response has a top-level data key; errors appear under errors per the GraphQL specification.
  • Compression: Responses are gzip-compressed when the client sends Accept-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

KindName
Root queryQuery
Object typesEvent, Venue, Artist, PublicArtistLink, Image, ImageVariant, EventConnection, EventEdge, PageInfo
Input typesEventSearchArguments
ScalarsInt, 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
): EventConnection

Arguments

ArgumentTypeRequiredDescription
filterEventSearchArgumentsyesCriteria used to narrow the result set. See EventSearchArguments.
firstIntnoNumber of events to return. Defaults to 30. Capped at 60.
afterStringnoCursor — return the page of events immediately after the one identified by this cursor.
beforeStringnoCursor — 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:

  1. Issue a first request with no after cursor and your desired first page size.
  2. Read pageInfo.endCursor from the response.
  3. If pageInfo.hasNextPage is true, issue the next request with after: <endCursor>.
  4. Repeat until hasNextPage is false.

Notes specific to this API:

  • first page size. Defaults to 30, maximum 60. Values above 60 are silently clamped.
  • last is not supported. Sending last will result in a GraphQL execution error: `last` pagination is not supported. Use first with after / before instead.
  • hasPreviousPage is always false. The connection only supports forward traversal semantics; do not rely on hasPreviousPage as 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.

FieldTypeRequiredDescription
accountIds[Int]yesScope the search to these VenuePilot account ids. Pass [-1] to return events across all accounts on the platform.
startDateStringnoLower bound on the event date, inclusive. Accepts YYYY-MM-DD. Defaults to no lower bound.
endDateStringnoUpper bound on the event date, inclusive. Accepts YYYY-MM-DD. Defaults to no upper bound.
idIntnoReturn only the event with this internal id. Mutually exclusive with slug; if both are given, id wins.
slugStringnoReturn only the event with this slug.
searchStringnoFree-text search across event name, artist name, and tags.
lastUpdatedAtISO8601DateTimenoReturn 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/endDate apply 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.
  • lastUpdatedAt does not compare against the field returned as updatedAt. Server-side, the filter is evaluated against a composite column that takes the maximum of updated_at across 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-level updatedAt is older than the cursor you passed in; that is expected behavior.

Object types

Event

A publicly-announced event.

FieldTypeNullableDescription
idIntnoInternal numeric id of the event.
slugStringnoURL-safe unique identifier.
nameStringnoDisplay name of the event.
startTimeISO8601DateTimeyesScheduled start time, always in UTC.
endTimeISO8601DateTimeyesScheduled end time, always in UTC.
doorTimeISO8601DateTimeyesDoors-open time, always in UTC.
minimumAgeIntyesMinimum age required to attend.
promoterStringyesName of the promoter or organizer.
supportStringyesFree-text list of supporting acts.
descriptionStringyesFull event description / program copy.
footerContentStringyesSupplementary copy shown in the event footer.
ticketsUrlStringyesURL where tickets can be purchased.
openInStringyesHow the tickets link should open. Currently always "new_tab".
buttonTextStringyesCustom label for the ticket purchase button.
venueVenueyesVenue at which the event takes place.
artists[Artist!]yesArtists performing at the event.
images[Image!]yesCover images associated with the event.
tags[String!]yesTags associated with the event.
canceledBooleanyestrue if the event has been canceled.
soldOutBooleanyestrue if the event is sold out.
priceMinFloatyesMinimum ticket price, denominated in venue.currency.
priceMaxFloatyesMaximum ticket price, denominated in venue.currency.
updatedAtISO8601DateTimeyesLast modified timestamp of the event record. ISO 8601 with offset; not normalised to UTC.

Notes:

  • startTime, endTime, and doorTime are normalised to UTC. They are always serialized as UTC ISO8601DateTime values (...Z). To display them to a user, convert them using venue.timezone.
  • updatedAt is 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. priceMin and priceMax use venue.currency; they are returned as plain floats with no currency metadata attached to the field itself.
  • Incremental sync. Use the lastUpdatedAt filter 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 the updatedAt field returned on the event — see EventSearchArguments for the full explanation.

Venue

Where an event takes place.

FieldTypeNullableDescription
idIntnoInternal numeric id of the venue.
nameStringyesDisplay name of the venue.
street1StringyesPrimary street address line.
street2StringyesSecondary address line (suite, unit, floor).
cityStringyesCity.
stateStringyesState, province, or region.
postalCodeStringyesPostal or ZIP code.
countryStringyesCountry (ISO 3166 alpha-2 code when set).
latitudeStringyesLatitude coordinate as a decimal string.
longitudeStringyesLongitude coordinate as a decimal string.
logoUrlStringyesURL of the venue logo. When the venue has no logo uploaded, a default VenuePilot logo URL is returned, so this field is rarely empty.
localeStringyesPreferred locale (e.g. en, de).
currencyStringyesISO 4217 currency code (e.g. usd, eur).
timezoneStringyesIANA timezone identifier (e.g. America/New_York).
updatedAtISO8601DateTimeyesLast modified timestamp of the venue record. ISO 8601 with offset; not normalised to UTC.

Notes:

  • latitude and longitude are strings, not floats, to preserve the exact precision supplied by venue operators.
  • timezone is the authoritative field for converting an event's UTC times into local venue time.

Artist

A performing artist attached to an event.

FieldTypeNullableDescription
idIntyesInternal numeric id of the artist.
nameStringnoDisplay name of the artist.
links[PublicArtistLink!]yesExternal links associated with the artist.
updatedAtISO8601DateTimeyesLast modified timestamp of the artist record.

A single external link for an artist (website, social profile, etc.).

FieldTypeNullableDescription
urlStringnoThe URL of the artist's external profile or website.

Image

An image associated with an event.

FieldTypeNullableDescription
nameStringnoDescriptive name or label for the image.
highlightedBooleanyestrue if the image should be emphasized in the UI.
variants[ImageVariant!]yesPre-rendered variants of this image at different sizes.

Notes:

  • An Image is a logical asset; each ImageVariant is a concrete URL at a specific size. Variants without a source are omitted from the list.

ImageVariant

A concrete, sized version of an Image.

FieldTypeNullableDescription
srcStringnoURL of the image for this variant.
variantStringnoIdentifier for the variant, e.g. thumb, medium, large.

EventConnection

Relay-style connection returned from the events query.

FieldTypeNullableDescription
edges[EventEdge]yesList of edges containing events and their cursors.
nodes[Event]yesShortcut to the underlying event nodes.
pageInfoPageInfonoPagination metadata for the current page.

EventEdge

A single edge in an EventConnection.

FieldTypeNullableDescription
nodeEventyesThe event associated with this edge.
cursorStringnoOpaque cursor identifying this edge in the current result set.

PageInfo

Standard Relay pagination metadata.

FieldTypeNullableDescription
hasNextPageBooleannotrue if another page of events exists after the current one.
hasPreviousPageBooleannoAlways false for this API. Do not rely on this as a backward indicator.
startCursorStringyesCursor pointing at the first edge in the current page.
endCursorStringyesCursor pointing at the last edge in the current page.

Scalars

ScalarDescription
IntSigned 32-bit integer.
FloatIEEE 754 double-precision floating point number.
StringUTF-8 character sequence.
Booleantrue or false.
ISO8601DateTimeDate-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] } }
}
JSON

Response (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"
            }
          }
        }
      ]
    }
  }
}
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-id header missing.
  • x-vp-app-id value 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:

SituationMessageHow to recover
Using last for pagination.`last` pagination is not supportedSwitch 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: 200 will 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:) or artist(id:) query; traverse from an event instead.
  • hasPreviousPage is always false. 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.currency to format for display.
  • updatedAt on the event lags lastUpdatedAt. Filtering by lastUpdatedAt may return events whose top-level updatedAt is 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

VersionDateNotes
1.02026-04-10Initial published version of this document.