BillyDocumentation

List offers

Retrieves a paginated list of the offers belonging to your Billy organization, in creation order, with their pricings and their stock lines. An offer is a `TICKET`, a `PRODUCT`, a `BUNDLE` of other offers or an `EXTRA` sold with a ticket; `type` discriminates the four shapes. The `offerId` and `pricingId` returned here are exactly the ones carried by `GET /tickets`, which is what makes a ticket resolvable against this catalog. **`?resaleEnabled=` returns a partial view.** It selects offers *and* prunes their `pricing[]`: a matching offer comes back carrying only the pricings that matched, so the same `offerId` has different contents depending on the query. Do not cache a filtered response as the offer's pricing list — a consumer that does will conclude the student pricing does not exist. `GET /catalog/offers/{offerId}` takes no filter and is the complete view. **Options.** A `TICKET`, `PRODUCT` or `BUNDLE` offer lists in `optionOffers` the `TICKET` and `PRODUCT` offers proposed as options with it, each with the pricing it is sold at there. A `TICKET` option is on the session of the base ticket or of the pack's tickets, and on any session the sales channel sells under a `PRODUCT` or a pack that holds no ticket; a `PRODUCT` option is in the variant the buyer picks. A ticket or a product inside a pack keeps its own options, and an offer is never an option both of a pack and of one of its tickets or products. That pricing is an ordinary entry of the option offer's `pricing[]`, and may also be sold alone, on the sales channels that select it. The pairs sit on the base offers, so read the whole event (`?eventId=`) to find the base offers that propose an offer. Each pair also says how many units of the option one unit of the base offer takes, a ticket, a product or a pack, `minQuantityPerBase` to `maxQuantityPerBase`: a minimum above 0 makes the option mandatory, and the base offer is then never sold without it. **Extras.** A `TICKET` offer lists in `extras` the `EXTRA` offers proposed with its tickets — a parking space, a meal — each with how many units of it a ticket carries, `minQuantityPerTicket` to `maxQuantityPerTicket`; a minimum above 0 makes the extra mandatory. `minDistinctExtrasPerTicket` and `maxDistinctExtrasPerTicket` bound how many different extras one ticket carries, whatever their units. An extra is listed once, however many ticket offers propose it, and is never sold alone or inside a bundle: its stock per session is its own, its single pricing is flat (`pricingId`, `price`), as a bundle's is, and it reports no quantity per order, since a ticket takes it in the range its offer's `extras` sets.

Preview release

This operation is only available on the preview release target — send Billy-API-Version: preview to reach it. Preview has no stability promise: its routes and payloads can change without notice, so pin a dated release before you depend on one.
GET
/v1/catalog/offers

Retrieves a paginated list of the offers belonging to your Billy organization, in creation order, with their pricings and their stock lines. An offer is a TICKET, a PRODUCT, a BUNDLE of other offers or an EXTRA sold with a ticket; type discriminates the four shapes. The offerId and pricingId returned here are exactly the ones carried by GET /tickets, which is what makes a ticket resolvable against this catalog.

?resaleEnabled= returns a partial view. It selects offers and prunes their pricing[]: a matching offer comes back carrying only the pricings that matched, so the same offerId has different contents depending on the query. Do not cache a filtered response as the offer's pricing list — a consumer that does will conclude the student pricing does not exist. GET /catalog/offers/{offerId} takes no filter and is the complete view.

Options. A TICKET, PRODUCT or BUNDLE offer lists in optionOffers the TICKET and PRODUCT offers proposed as options with it, each with the pricing it is sold at there. A TICKET option is on the session of the base ticket or of the pack's tickets, and on any session the sales channel sells under a PRODUCT or a pack that holds no ticket; a PRODUCT option is in the variant the buyer picks. A ticket or a product inside a pack keeps its own options, and an offer is never an option both of a pack and of one of its tickets or products. That pricing is an ordinary entry of the option offer's pricing[], and may also be sold alone, on the sales channels that select it. The pairs sit on the base offers, so read the whole event (?eventId=) to find the base offers that propose an offer. Each pair also says how many units of the option one unit of the base offer takes, a ticket, a product or a pack, minQuantityPerBase to maxQuantityPerBase: a minimum above 0 makes the option mandatory, and the base offer is then never sold without it.

Extras. A TICKET offer lists in extras the EXTRA offers proposed with its tickets — a parking space, a meal — each with how many units of it a ticket carries, minQuantityPerTicket to maxQuantityPerTicket; a minimum above 0 makes the extra mandatory. minDistinctExtrasPerTicket and maxDistinctExtrasPerTicket bound how many different extras one ticket carries, whatever their units. An extra is listed once, however many ticket offers propose it, and is never sold alone or inside a bundle: its stock per session is its own, its single pricing is flat (pricingId, price), as a bundle's is, and it reports no quantity per order, since a ticket takes it in the range its offer's extras sets.

Authorization

HTTPBearer
AuthorizationBearer <token>

API key authentication. Format: Bearer billy:o:<organization_id>:<api_key>

Example: Bearer billy:o:yyyyyyyyyyyyyyyy:xxxxxxxxxxxxxxxxx

In: header

Query Parameters

page?Page

Page index (1-based, e.g., 1 for first page, 2 for second page)

Range1 <= value
Default1
perPage?|

Maximum number of records to return

createdAt.gte?|

Return results where createdAt is at or after this value. For datetime fields, use ISO 8601 format with timezone offset (e.g. 2026-03-01T00:00:00+02:00).

createdAt.lte?|

Return results where createdAt is at or before this value. For datetime fields, use ISO 8601 format with timezone offset (e.g. 2026-03-01T00:00:00+02:00).

eventId?|

Return only the offers of this event — tickets and extras, and the products and bundles the event currently holds. Only a TICKET or an EXTRA reports the eventId back, so a filtered PRODUCT or BUNDLE cannot be checked against this value from its payload; that is deliberate, since their event scoping is a storage fact today rather than a promise.

sessionId?|

Narrow inventory[] on ticket and extra offers to this session. Does not remove offers from the list.

type?|

Return only offers of this type. type=BUNDLE is the bundles-only list, type=EXTRA the extras-only one.

resaleEnabled?|

Select offers and prune their pricing[]: an offer is returned when at least one of its pricings matches, carrying only those pricings. Since resaleEnabled exists only on the pricings of a TICKET offer, this filter never returns a PRODUCT or a BUNDLE, whatever value is passed.

Header Parameters

Billy-API-Version*string

Requested API version. Current context: preview.

Default"preview"

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/catalog/offers" \  -H "Billy-API-Version: preview"
{  "items": [    {      "offerId": "331704d8-50bf-4e99-8d82-95c4974b1a2b",      "type": "TICKET",      "name": "Pass 2 jours",      "description": "<p>Accès aux deux journées</p>",      "eventId": "168c36ec-2e28-4312-be93-8893f0cc4d5e",      "currency": "EUR",      "images": [],      "tags": [        "pass"      ],      "minQuantityPerOrder": 1,      "maxQuantityPerOrder": 10,      "compareAtPrice": 55,      "createdAt": "2026-08-01T10:00:00+00:00",      "vatRate": 0.2,      "defaultMaximumCapacity": 500,      "attendeeFormId": "5d6e7f80-9a0b-4c1d-8e2f-3a4b5c6d7e8f",      "pricing": [        {          "pricingId": "9f2c1b44-77aa-4f10-9c3e-0d1b2e3f4a5b",          "name": "Tarif plein",          "price": 45,          "bundleOfferId": null,          "resaleEnabled": true        },        {          "pricingId": "b3e7d901-1c22-4a55-8f6d-7e8f9a0b1c2d",          "name": "Tarif étudiant",          "price": 32,          "bundleOfferId": null,          "resaleEnabled": false        },        {          "pricingId": "c4f8ea12-2d33-4b66-9a7e-8f9a0b1c2d3e",          "name": "Pass festival 3 dates",          "price": 36,          "bundleOfferId": "7a1b2c3d-4e5f-4061-8172-93a4b5c6d7e8",          "resaleEnabled": true        }      ],      "optionOffers": [        {          "offerId": "4c5d6e7f-8091-4a2b-b3c4-d5e6f7a8b9c0",          "pricingId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",          "minQuantityPerBase": 0,          "maxQuantityPerBase": 2        }      ],      "extras": [        {          "offerId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",          "minQuantityPerTicket": 0,          "maxQuantityPerTicket": 3        }      ],      "minDistinctExtrasPerTicket": 0,      "maxDistinctExtrasPerTicket": null,      "inventory": [        {          "inventoryId": "e1f2a3b4-c5d6-4e7f-8091-a2b3c4d5e6f7",          "sessionId": "11223344-5566-4778-899a-bbccddeeff00",          "totalStock": 500,          "availableStock": 412        }      ]    },    {      "offerId": "2b3c4d5e-6f70-4812-9345-56789abcdef0",      "type": "PRODUCT",      "name": "T-shirt officiel",      "description": "Coton bio, sérigraphie",      "currency": "EUR",      "images": [        "https://cdn.billyapp.live/tshirt.png"      ],      "tags": [        "merch"      ],      "minQuantityPerOrder": 1,      "maxQuantityPerOrder": 5,      "compareAtPrice": null,      "createdAt": "2026-08-02T08:00:00+00:00",      "vatRate": 0.2,      "defaultQuantity": 100,      "variantOptionLabels": {        "option1": "Taille",        "option2": "Couleur",        "option3": null      },      "pricing": [        {          "pricingId": "8c9d0e1f-2a3b-4c5d-8e6f-7a8b9c0d1e2f",          "name": "Prix boutique",          "price": 25,          "bundleOfferId": null        }      ],      "optionOffers": [        {          "offerId": "8e9f0a1b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",          "pricingId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2e",          "minQuantityPerBase": 0,          "maxQuantityPerBase": 2        }      ],      "inventory": [        {          "inventoryId": "3d4e5f60-7182-4939-a4b5-c6d7e8f90a1b",          "option1": "M",          "option2": "Noir",          "option3": null,          "totalStock": 100,          "availableStock": 73,          "weightInGrams": 180        }      ]    },    {      "offerId": "7a1b2c3d-4e5f-4061-8172-93a4b5c6d7e8",      "type": "BUNDLE",      "name": "Pass festival 3 dates",      "description": null,      "currency": "EUR",      "images": [],      "tags": [],      "minQuantityPerOrder": 1,      "maxQuantityPerOrder": 4,      "compareAtPrice": 135,      "createdAt": "2026-08-02T09:12:00+00:00",      "pricingId": "c4f8ea12-2d33-4b66-9a7e-8f9a0b1c2d3e",      "price": 108,      "contains": [        {          "offerId": "331704d8-50bf-4e99-8d82-95c4974b1a2b",          "quantity": 3,          "price": 36        }      ],      "optionOffers": [        {          "offerId": "8e9f0a1b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",          "pricingId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2e",          "minQuantityPerBase": 1,          "maxQuantityPerBase": 1        }      ]    },    {      "offerId": "4c5d6e7f-8091-4a2b-b3c4-d5e6f7a8b9c0",      "type": "TICKET",      "name": "Place accompagnant",      "description": null,      "eventId": "168c36ec-2e28-4312-be93-8893f0cc4d5e",      "currency": "EUR",      "images": [],      "tags": [],      "minQuantityPerOrder": 0,      "maxQuantityPerOrder": 4,      "compareAtPrice": null,      "createdAt": "2026-08-02T10:30:00+00:00",      "vatRate": 0.2,      "defaultMaximumCapacity": 50,      "attendeeFormId": null,      "pricing": [        {          "pricingId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",          "name": "Tarif accompagnant",          "price": 6,          "bundleOfferId": null,          "resaleEnabled": false        }      ],      "optionOffers": [],      "extras": [],      "minDistinctExtrasPerTicket": 0,      "maxDistinctExtrasPerTicket": null,      "inventory": [        {          "inventoryId": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",          "sessionId": "11223344-5566-4778-899a-bbccddeeff00",          "totalStock": 50,          "availableStock": 38        }      ]    },    {      "offerId": "8e9f0a1b-2c3d-4e5f-9a6b-7c8d9e0f1a2b",      "type": "PRODUCT",      "name": "Gobelet réutilisable",      "description": null,      "currency": "EUR",      "images": [],      "tags": [],      "minQuantityPerOrder": 0,      "maxQuantityPerOrder": 10,      "compareAtPrice": null,      "createdAt": "2026-08-02T11:00:00+00:00",      "vatRate": 0.2,      "defaultQuantity": 1000,      "variantOptionLabels": {        "option1": null,        "option2": null,        "option3": null      },      "pricing": [        {          "pricingId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2e",          "name": "Tarif gobelet",          "price": 3,          "bundleOfferId": null        }      ],      "optionOffers": [],      "inventory": [        {          "inventoryId": "a0b1c2d3-e4f5-4a6b-9c7d-8e9f0a1b2c3d",          "option1": null,          "option2": null,          "option3": null,          "totalStock": 1000,          "availableStock": 864,          "weightInGrams": 40        }      ]    },    {      "offerId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",      "type": "EXTRA",      "name": "Boissons",      "description": "Une boisson au bar",      "eventId": "168c36ec-2e28-4312-be93-8893f0cc4d5e",      "currency": "EUR",      "images": [],      "tags": [],      "compareAtPrice": null,      "createdAt": "2026-08-03T09:00:00+00:00",      "vatRate": 0.1,      "pricingId": "1b2c3d4e-5f60-4a7b-9c8d-0e1f2a3b4c5d",      "price": 4,      "inventory": [        {          "inventoryId": "2c3d4e5f-6071-4b8c-ad9e-1f2a3b4c5d6e",          "sessionId": "11223344-5566-4778-899a-bbccddeeff00",          "totalStock": 1500,          "availableStock": 1260        }      ]    }  ],  "page": 1,  "perPage": 100,  "totalPages": 1,  "hasMore": false,  "nextPage": null}