Eventjet GraphQL API documentation

Report

Object

A table of rows and columns built from your event data — sales, tickets, attendees, and more.

Building a reporting integration

The reporting API lets you query tabular event data with configurable columns, filters, sorting, and aggregation.

What is a report?

A Report is a table of rows and columns built from your event data. Each column corresponds to a data point (a metric), and each row is a ReportLine containing one ReportCell per column.

The available report types are determined by your account. Common types include "sales", "tickets", and "events". Each type has its own set of metrics, which you can discover via metrics.

At a glance

These are the root fields and mutations you use to interact with reports:

1. Starting a report

To fetch a report you call Query.report. You must provide either:

  • type — a report type key such as "sales". This returns the default configuration for that type.
  • id — an opaque id string you previously received from the API. This restores an exact report configuration, including any filters, sorting, and column choices you applied.

Start with type when building a UI from scratch. Use id when you want to bookmark or share a specific view, or when you navigate between pages of the same report.

The available type keys (such as "sales", "tickets", or "events") depend on your account configuration. The type of the report you are currently looking at is readable via type.

query GetReport($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    id           # Opaque string; pass this back as `id` to restore this exact configuration.
    numberOfLines
    columnsV2 {
      label
      typeHints
    }
    lines {
      cells {
        value
        typeHints
        label
      }
    }
  }
}
query GetReport($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    id           # Opaque string; pass this back as `id` to restore this exact configuration.
    numberOfLines
    columnsV2 {
      label
      typeHints
    }
    lines {
      cells {
        value
        typeHints
        label
      }
    }
  }
}

2. Reading cells and type hints

ReportCell.value is always a plain string (or null). The ReportCell.typeHints list tells you how to interpret it:

Type hintMeaning
"money"Integer amount in the smallest currency unit (e.g. cents).
"datetime"Unix timestamp (seconds since epoch).
"comma_separated_list"Multiple values joined by commas.
"key_value" + "json_encoded"JSON-encoded object with string values.
"minmax"Aggregated range formatted as "min/max" (e.g. "100/500").
"cardinality"Number of distinct values in an aggregated group.
"row_count"Number of rows in an aggregated group.

Use ReportCell.label whenever it is non-null — it contains a localized, human-readable string ready to display. For example, when ReportCell.value is an event ID, ReportCell.label is the event's name in the requested locale.

ReportColumn.typeHints reflects the same hints at the column level, so you can determine the rendering strategy before iterating over rows.

3. Pagination

Use first and offset on Query.report to page through results. Use numberOfLines to get the total row count for building page controls.

offset must be a multiple of first — pagination works in whole pages, and an offset that falls between page boundaries is rounded down to the previous multiple of first.

query GetReportPage($first: Int!, $offset: Int!, $locale: Locale!) {
  report(type: "sales", first: $first, offset: $offset, locale: $locale) {
    numberOfLines   # Total rows matching the current filters — use this to calculate page count.
    lines {
      cells {
        value
        label
        typeHints
      }
    }
  }
}
query GetReportPage($first: Int!, $offset: Int!, $locale: Locale!) {
  report(type: "sales", first: $first, offset: $offset, locale: $locale) {
    numberOfLines   # Total rows matching the current filters — use this to calculate page count.
    lines {
      cells {
        value
        label
        typeHints
      }
    }
  }
}

4. Filtering

Filters narrow the rows in the report. Pass them as arguments to Query.report:

  • rangeFilters — numeric min/max bounds (e.g. sale amount, number of guests).
  • wildcardFilters — text search against a metric (e.g. order number, customer name).

Each filter entry requires a ReportMetric.key to identify which metric to filter. You can discover available filters and their types by inspecting filters or ReportColumn.filters.

query GetFilteredReport($locale: Locale!, $first: Int!, $offset: Int!) {
  report(
    type: "sales",
    first: $first,
    offset: $offset,
    locale: $locale,
    rangeFilters: [{ key: "amount", min: 1000, max: 5000 }],  # Amount in cents
    wildcardFilters: [{ key: "order_number", query: "j9i5" }]
  ) {
    numberOfLines
    filters {
      key
      label(locale: $locale)
      typeHints
      valueLabel(locale: $locale)  # Human-readable summary of the active filter value.
      ...on RangeReportFilter {
        currentMin
        currentMax
      }
      ...on WildcardReportFilter {
        query
      }
      ...on OptionsReportFilter {
        selectedOptions {
          label(locale: $locale)
        }
      }
    }
    lines {
      cells { value label typeHints }
    }
  }
}
query GetFilteredReport($locale: Locale!, $first: Int!, $offset: Int!) {
  report(
    type: "sales",
    first: $first,
    offset: $offset,
    locale: $locale,
    rangeFilters: [{ key: "amount", min: 1000, max: 5000 }],  # Amount in cents
    wildcardFilters: [{ key: "order_number", query: "j9i5" }]
  ) {
    numberOfLines
    filters {
      key
      label(locale: $locale)
      typeHints
      valueLabel(locale: $locale)  # Human-readable summary of the active filter value.
      ...on RangeReportFilter {
        currentMin
        currentMax
      }
      ...on WildcardReportFilter {
        query
      }
      ...on OptionsReportFilter {
        selectedOptions {
          label(locale: $locale)
        }
      }
    }
    lines {
      cells { value label typeHints }
    }
  }
}

To clear a filter, traverse ReportFilter.cleared — it returns a new Report with that filter removed. See the next section for how this pattern works.

5. The functional navigation pattern

This is the most important thing to understand about the reporting API: you do not call mutations to change the report configuration. Instead, you read a modified Report by traversing special fields on columns, filters, and aggregation options. Each such field resolves to a brand-new Report that reflects the change.

This means a single query can fetch both the current report data and the "what would this look like if sorted by column X" state — without any extra round-trips.

query ReportWithNavigation($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    id
    numberOfLines
    columnsV2 {
      label
      typeHints
      sortDirection           # Current sort direction for this column, if any.
      sortedAscending {       # Non-null when the column is sortable.
        id                    # Pass this id back to Query.report to apply the sort.
      }
      sortedDescending {
        id
      }
      removed {               # The report without this column.
        id
      }
    }
    filters {
      key
      label(locale: $locale)
      cleared {               # The report with this filter removed.
        id
      }
    }
    aggregationOptions {
      label(locale: $locale)
      isEnabled
      report {                # The report grouped by this aggregation option.
        id
      }
    }
    withoutAggregations {     # The report with all aggregations removed.
      id
    }
  }
}
query ReportWithNavigation($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    id
    numberOfLines
    columnsV2 {
      label
      typeHints
      sortDirection           # Current sort direction for this column, if any.
      sortedAscending {       # Non-null when the column is sortable.
        id                    # Pass this id back to Query.report to apply the sort.
      }
      sortedDescending {
        id
      }
      removed {               # The report without this column.
        id
      }
    }
    filters {
      key
      label(locale: $locale)
      cleared {               # The report with this filter removed.
        id
      }
    }
    aggregationOptions {
      label(locale: $locale)
      isEnabled
      report {                # The report grouped by this aggregation option.
        id
      }
    }
    withoutAggregations {     # The report with all aggregations removed.
      id
    }
  }
}

When you want to apply a navigation step (e.g. the user clicks a column header to sort), take the id from the relevant nested Report and pass it back as the id argument to Query.report:

query ApplyNavigation($reportId: ID!, $locale: Locale!, $first: Int!, $offset: Int!) {
  report(id: $reportId, locale: $locale, first: $first, offset: $offset) {
    id
    numberOfLines
    sorting {
      key
      direction
    }
    lines {
      cells { value label typeHints }
    }
  }
}
query ApplyNavigation($reportId: ID!, $locale: Locale!, $first: Int!, $offset: Int!) {
  report(id: $reportId, locale: $locale, first: $first, offset: $offset) {
    id
    numberOfLines
    sorting {
      key
      direction
    }
    lines {
      cells { value label typeHints }
    }
  }
}

6. Sorting via arguments

As an alternative to the functional navigation pattern, you can pass the sort argument directly to Query.report. Each entry is a SortInput with a metric key and an optional direction (ASC or DESC, defaulting to ASC).

Only sortable metrics can be used — check that ReportColumn.sortedAscending is non-null, or that ReportMetric.isSortable is true, to determine which columns support sorting.

query GetSortedReport($locale: Locale!) {
  report(
    type: "sales",
    locale: $locale,
    sort: [{ metric: "amount", direction: DESC }]
  ) {
    id
    sorting {
      key
      direction
    }
    lines {
      cells { value label typeHints }
    }
  }
}
query GetSortedReport($locale: Locale!) {
  report(
    type: "sales",
    locale: $locale,
    sort: [{ metric: "amount", direction: DESC }]
  ) {
    id
    sorting {
      key
      direction
    }
    lines {
      cells { value label typeHints }
    }
  }
}

7. Aggregation

Aggregation groups rows by one or more dimensions (e.g. by event or by date) and summarizes the numeric columns. Use aggregationOptions to discover available groupings and to navigate to aggregated variants of the current report.

When a report is aggregated, ReportColumn.isAggregated is true for the grouping columns, and numeric summary cells may carry additional type hints such as "minmax", "cardinality", or "row_count" depending on the summary mode of each metric.

query GetAggregationOptions($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    aggregationOptions {
      label(locale: $locale)
      isEnabled
      report {
        id           # Pass this to Query.report to view the aggregated report.
      }
    }
    withoutAggregations {
      id             # Pass this to remove all groupings.
    }
  }
}
query GetAggregationOptions($locale: Locale!) {
  report(type: "sales", locale: $locale) {
    aggregationOptions {
      label(locale: $locale)
      isEnabled
      report {
        id           # Pass this to Query.report to view the aggregated report.
      }
    }
    withoutAggregations {
      id             # Pass this to remove all groupings.
    }
  }
}

8. Saved views

A ReportView is a named snapshot of a report configuration — a label attached to a id. Users (or your application) can save, rename, and delete views.

Use Query.reportViews to list all views for the current user:

query GetReportViews($locale: Locale!) {
  reportViews(locale: $locale) {
    id
    label
    report {
      id
      numberOfLines
      columnsV2 { label typeHints }
      lines {
        cells { value label typeHints }
      }
    }
  }
}
query GetReportViews($locale: Locale!) {
  reportViews(locale: $locale) {
    id
    label
    report {
      id
      numberOfLines
      columnsV2 { label typeHints }
      lines {
        cells { value label typeHints }
      }
    }
  }
}

To create a view from the current report configuration, pass the id you want to save:

mutation SaveView($reportId: ID!, $label: String!) {
  createReportView(input: { reportId: $reportId, label: $label }) {
    view {
      id
      label
    }
  }
}
mutation SaveView($reportId: ID!, $label: String!) {
  createReportView(input: { reportId: $reportId, label: $label }) {
    view {
      id
      label
    }
  }
}

To rename a view or point it at a new report configuration:

mutation UpdateView($viewId: ID!, $label: String, $reportId: ID) {
  modifyReportView(input: { viewId: $viewId, label: $label, reportId: $reportId }) {
    view {
      id
      label
    }
  }
}
mutation UpdateView($viewId: ID!, $label: String, $reportId: ID) {
  modifyReportView(input: { viewId: $viewId, label: $label, reportId: $reportId }) {
    view {
      id
      label
    }
  }
}

To delete a view:

mutation RemoveView($viewId: ID!) {
  deleteReportView(input: { viewId: $viewId }) {
    success
  }
}
mutation RemoveView($viewId: ID!) {
  deleteReportView(input: { viewId: $viewId }) {
    success
  }
}

9. Gotchas

Omitting locale silently falls back to English. When you omit the locale argument on Query.report, ReportCell.label and ReportColumn.label come back in English rather than the user's language. Note that fields like ReportFilter.label take their own required locale argument instead. Always pass a locale when your goal is to display the report to a user.

ReportCell.value is always a raw string — never display it directly. A money cell's value is an integer in the smallest currency unit (e.g. "1250" means €12.50 in a euro account). Check ReportCell.typeHints first and format accordingly. Use ReportCell.label instead when a pre-formatted display string is sufficient.

Aggregated cells have different ReportCell.typeHints than plain cells. When a report is grouped, a "money" column no longer holds a single amount — it may hold a "minmax" range ("1000/5000") or a "cardinality" count. Read ReportCell.typeHints per-cell, not just per-column, when working with aggregated data.

offset is rounded down to a multiple of first — and ignored without first. Pagination works in whole pages: an offset that is not a multiple of first is silently rounded down to the previous page boundary (e.g. offset: 150, first: 100 returns rows 100–199, not 150–249). Passing offset without first has no effect. Always page in steps of first, starting at 0.

OptionsReportFilter cannot be applied via Query.report arguments. The rangeFilters and wildcardFilters arguments only cover range and text filters. To apply a categorical filter (e.g. filter by specific events), navigate to ReportFilterOption.report and pass the resulting id back to Query.report. See the options filter recipe below.

Unknown or unusable keys in sort, rangeFilters, and wildcardFilters are silently ignored. Entries whose key (or metric, for sort) does not match a metric of the report type produce no error — the report simply comes back unfiltered or unsorted. The same goes for sort entries that reference a non-sortable metric. A typo'd key gives you no signal, so validate keys against metrics and check ReportMetric.isSortable before sorting.

id is not a stable identifier — it encodes the current configuration. Every navigation step (sort, filter change, column toggle) produces a new id. Don't store it as a permanent reference to a report type; store it only as a bookmark to restore a specific configuration. For a named, persistent snapshot, use saved views instead.

System-provided ReportViews have a null ReportView.id. Only user-created views have a ReportView.id and can be modified or deleted. Always check for null before passing ReportView.id to modifyReportView or deleteReportView.

Deprecated fields ReportLine.values and ReportLine.labels omit type hints. They exist for backwards compatibility. Use ReportLine.cells — each cell has a ReportCell.value, ReportCell.typeHints, and an optional localized ReportCell.label.

10. Recipes

Export all rows as CSV

Paginate through all rows until you have fetched the full result set. Use numberOfLines to know when to stop.

query ExportPage($type: String!, $locale: Locale!, $first: Int!, $offset: Int!) {
  report(type: $type, locale: $locale, first: $first, offset: $offset) {
    numberOfLines    # Total rows — compare to offset + first to detect the last page.
    columnsV2 {
      label          # Column header
    }
    lines {
      cells {
        value        # Raw string value; format using typeHints.
        label        # Pre-formatted display string; use this when available.
        typeHints    # Tells you how to interpret value (e.g. "money", "datetime").
      }
    }
  }
}
query ExportPage($type: String!, $locale: Locale!, $first: Int!, $offset: Int!) {
  report(type: $type, locale: $locale, first: $first, offset: $offset) {
    numberOfLines    # Total rows — compare to offset + first to detect the last page.
    columnsV2 {
      label          # Column header
    }
    lines {
      cells {
        value        # Raw string value; format using typeHints.
        label        # Pre-formatted display string; use this when available.
        typeHints    # Tells you how to interpret value (e.g. "money", "datetime").
      }
    }
  }
}
page 1:  offset=0,   first=200
page 2:  offset=200, first=200
page N:  offset = (N-1) * 200
stop when: offset + 200 >= numberOfLines

Build a column selector

Use metricConnections to enumerate all available metrics and let users add or remove columns. ReportMetricConnection.toggledColumn returns the Report you'd get after clicking the toggle.

query GetColumnSelector($reportId: ID!, $locale: Locale!) {
  report(id: $reportId, locale: $locale) {
    metricConnections {
      metric {
        key
        label(locale: $locale)
        typeHints
      }
      isColumnEnabled    # true when this metric is currently a visible column.
      toggledColumn {
        id               # Pass this id back to Query.report to apply the toggle.
      }
    }
  }
}
query GetColumnSelector($reportId: ID!, $locale: Locale!) {
  report(id: $reportId, locale: $locale) {
    metricConnections {
      metric {
        key
        label(locale: $locale)
        typeHints
      }
      isColumnEnabled    # true when this metric is currently a visible column.
      toggledColumn {
        id               # Pass this id back to Query.report to apply the toggle.
      }
    }
  }
}

When the user toggles a metric, take toggledColumn.id and issue a new Query.report(id: ...) call to reload the report with that column added or removed.

Apply a categorical (options) filter

OptionsReportFilter options are navigated, not passed as arguments. Fetch the available choices, then follow ReportFilterOption.report to get the filtered Report.

query GetFilterOptions($reportId: ID!, $filterKey: String!, $locale: Locale!) {
  report(id: $reportId, locale: $locale) {
    filter(key: $filterKey) {
      ... on OptionsReportFilter {
        label(locale: $locale)
        options {
          label(locale: $locale)   # Display name for this choice.
          report {
            id                     # Pass this id to load the report filtered to this option.
          }
        }
        selectedOptions {
          label(locale: $locale)   # Currently active selections.
        }
      }
    }
  }
}
query GetFilterOptions($reportId: ID!, $filterKey: String!, $locale: Locale!) {
  report(id: $reportId, locale: $locale) {
    filter(key: $filterKey) {
      ... on OptionsReportFilter {
        label(locale: $locale)
        options {
          label(locale: $locale)   # Display name for this choice.
          report {
            id                     # Pass this id to load the report filtered to this option.
          }
        }
        selectedOptions {
          label(locale: $locale)   # Currently active selections.
        }
      }
    }
  }
}

Pass id from the chosen option to Query.report as the id argument. To select multiple options, chain the navigations: take the id from the first option's report, load it, then follow the next option's id from there. To clear the filter, use ReportFilter.cleared.

Fields

FieldDescriptionType
aggregationOptionsThe grouping dimensions available for this report.[ ReportAggregationOption ]
columnsThe keys of the columns currently in the report. Deprecated — use `columnsV2` instead.[ String ]
columnsV2The columns currently included in the report, in display order.[ ReportColumn ]
filterReturns the filter identified by `ReportFilter.key`, or null if no filter with that key exists for this report.ReportFilter
filtersAll filters available for this report, whether or not they are currently active.[ ReportFilter ]
idAn opaque identifier that encodes the complete report configuration — type, columns, filters, sorting, and active aggregations.ID!
linesThe data rows for the current page. Each `ReportLine` contains one cell per column in the same order as `columnsV2`.[ ReportLine ]
metricConnectionsAll available metrics paired with their current column state.[ ReportMetricConnection ]
metricsFlat list of all metrics available for this report type.[ ReportMetric ]
numberOfLinesThe total number of rows matching the current filters, regardless of pagination.Int
sortingThe sort orders currently applied to the report. Each entry names a metric key and a direction.[ ReportSorting ]
summariesOne summary value per column, in the same order as `columnsV2`.[ String ]
typeThe report type, such as `"sales"` or `"events"`. The type determines which metrics are available and how rows are defined.ReportType
withoutAggregationsThe same report with all grouping removed, returning to individual row-level data.Report