Get tickets
One row per owned ticket. The wide, denormalised centre of the model — event, article, sale, buyer and holder all flattened onto each row.
If you load only one dataset, load this one. Most business questions are answerable from it without any join.
Grain and key
| Grain | one row per ticket |
| Natural key | MandatorInternalId, TicketInternalId |
| Page size | 500 |
| Columns | 342 |
| Watermark | none — full reload only |
The columns most extracts start with
A workable first projection. The families are mapped in Reading the columns, the token sets in Column values.
Identity
| Column | Type | Notes |
|---|---|---|
TicketInternalId | int | Key. Join target for usages and surveys. |
TicketUniqueId | Guid | Stable global reference. |
TicketNumber | string | The number printed on the ticket. |
Status and lifecycle
| Column | Type | Notes |
|---|---|---|
TicketIsCancelled | bool | Use this for cancellations, not the status column. |
TicketStatusType | token | NONE, FREE, ASSIGNED, BLOCKED, UNKNOWN. |
TicketValidity | token | NONE, ONE_DAY, MULTIPLE_DAYS, ALL_DAYS. |
TicketIsDayTicket / TicketIsPermanentTicket | bool | Validity shape as flags. |
TicketSoldAt / TicketUsedAt | datetimeoffset | Offset-aware; safe to convert. |
Money
| Column | Type | Notes |
|---|---|---|
TicketGrossPrice | decimal | The default for revenue questions. |
TicketNetPrice | decimal | Only when net was explicitly asked for. |
CurrencyShort | string | Always carry this alongside any amount. |
SalePaymentStatus | token | PAID is the honest filter for paid revenue. |
Event and article
| Column | Type | Notes |
|---|---|---|
FaireventInternalId | int | Best key for splitting large extracts. |
FaireventName / FaireventYear | string / int | Filter these two separately. |
FairBrandName | string | For year-over-year comparison. |
ArticleInternalId / ArticleName | int / string | Join key to StatisticGroups; main segmentation dimension. |
ArticleType | token | Filter eq 'TICKET' when counting tickets sold. |
Who and how it was issued
| Column | Type | Notes |
|---|---|---|
TicketOwner* | various | The person who attends. |
Buyer* | various | The person who paid. |
TicketIsIssuedByOrganizer | bool | Organiser route. |
TicketIsIssuedByExhibitor | bool | Exhibitor quota route. |
SaleDistributionChainType | token | Sales channel. |
Exhibitor* | various | Which exhibitor issued it. |
Marketing
| Column | Type | Notes |
|---|---|---|
SaleIsTest | bool | Filter eq false unless you want test sales. |
SaleUtm* | string | Campaign attribution, five columns. |
PromotionCode* | string | Discount or access code applied. |
Traps specific to this dataset
- Several currencies live in one column. EUR, USD, CHF, QAR and null all occur. Never add amounts across them; group by
CurrencyShort. - Not every row is a ticket.
ArticleTypealso covers vouchers, service fees and functional features. Filter it when you mean tickets. - Buyer is not owner. See People: buyer, holder, exhibitor.
- Organiser and exhibitor tickets are different populations. Averaging them together distorts every conversion figure.
- 107 of the columns are your own custom fields with generic labels. See Reading the columns.
- No per-ticket attendance flag. Attendance reporting goes through
StatisticGroups— see Counting visitors. - No watermark. Incremental loading is not possible; a nightly full reload is the practical answer.
Joining
| To | On | Grain effect |
|---|---|---|
TicketUsages | MandatorInternalId, TicketInternalId | one to many |
Surveys | MandatorInternalId, TicketInternalId | one to many |
StatisticGroups | MandatorInternalId, ArticleInternalId | one to many |
Full column catalogue
GET /odata/v1/$metadata — every column with its type, nullability and business label.
Request
No request body. All shaping is done with the query options below.
$select(string): the columns to load. A full row is 342 columns and ~10 KB — name what you need.$filter(string): an OData predicate. Start withSaleIsTest eq false.$orderby(string): sort order.$top(integer): maximum rows, capped at 1000.$count(boolean): include the total count. Scans the whole dataset.$skiptoken(string): server-generated paging token. Follow the next link rather than setting this.
Response
@odata.context(string): the metadata URL describing the shape of this payload.value(array): the page of ticket rows. Which columns appear depends on$select.@odata.nextLink(string, optional): the next page. Absent on the last page — that is your signal to stop.@odata.count(integer, optional): present only when$count=true.
Before you aggregate what comes back
Three things will otherwise give you wrong numbers, all covered in Getting the numbers right: several currencies live in CurrencyShort, test sales are present in production data, and not every row is a ticket — ArticleType also covers vouchers, catalogues and service fees.
Notes
Page size is 500. There is no by-key form of this endpoint — Tickets(...) answers 404 by design.
Status columns such as ArticleType and SalePaymentStatus carry UPPER_SNAKE_CASE tokens from a closed set — Datasets → Column values lists every one. 107 of the 342 columns are your own custom registration fields with generic labels; see Reading the columns.
Authentication#auth
This endpoint does not require authentication.
Headers#headers
Required. A client-credentials token carrying the scope ticketinghub-api and no subject. See Authentication.
Optional. JSON is returned by default.
Query parameters#query
Comma-separated list of columns to load. The single biggest lever on extract time: a full row is 342 columns and roughly 10 KB, and projecting down also lets the database skip work. Carry CurrencyShort whenever you carry an amount.
OData predicate. Token values are uppercase and case-sensitive — eq 'TICKET' works, eq 'ticket' matches nothing. Worth knowing: SaleIsTest eq false excludes test sales, ArticleType eq 'TICKET' keeps vouchers and service fees out of a ticket count, and FaireventInternalId eq <id> splits a very large extract into chunks that parallelise.
Sort order. Note that it also changes the order the keyset walk runs in.
Maximum number of rows. Capped at 1000; a higher value is rejected with 400. A smaller value lowers the page size but cannot raise it above 500.
Include the total row count in @odata.count. Useful once per load to size the job; it scans the whole dataset, so do not ask for it on every page.
Server-generated keyset paging token, carried in @odata.nextLink. Treat it as opaque and follow the link; hand-crafting one is unsupported.
Offset paging. Supported but discouraged: cost grows with depth, and rows written while you read can be skipped or repeated. Use @odata.nextLink instead.