API · Public events feed
The public events feed exposes the same listed events your customers see on your shop, as JSON, on the same URL, just request application/json. It’s designed for embedding your event lineup on your own website, in a partner site, or in a third-party tool.
No authentication, CORS-enabled, cacheable.
If you’re looking for the authenticated, account-wide API instead, drafts, orders, customers, write access, see the admin API.
Quick example
curl -H "Accept: application/json" \
https://your-subdomain.usetix.io/events
From a browser:
fetch("https://your-subdomain.usetix.io/events.json")
.then(r => r.json())
.then(data => console.log(data.events))
Endpoints
GET /events
Returns published, listed, upcoming events for the shop. Past, unpublished, and unlisted events are never included in this collection.
curl -H "Accept: application/json" \
https://your-subdomain.usetix.io/events
Response:
{
"events": [
{
"slug": "spring-showcase",
"title": "Spring Showcase",
"description": "<p>Our annual seasonal event.</p>",
"url": "https://your-subdomain.usetix.io/events/spring-showcase",
"starts_at": "2026-05-01T19:00:00Z",
"doors_open_at": "2026-05-01T18:00:00Z",
"ends_at": "2026-05-01T23:00:00Z",
"show_end_time": true,
"sales_starts_at": null,
"sales_ends_at": "2026-05-01T19:00:00Z",
"time_zone": "Berlin",
"minimum_age": 18,
"video_url": null,
"sold_out": false,
"image_url": "https://your-subdomain.usetix.io/rails/active_storage/blobs/...",
"background_image_url": null,
"gallery_image_urls": [],
"venue": {
"name": "The Venue",
"address": "Hauptstraße 1",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"latitude": null,
"longitude": null,
"time_zone": "Berlin"
},
"cheapest_available_price": {
"amount": "15.00",
"currency": "EUR"
}
}
]
}
GET /events/:slug
Returns a single published event with its tickets, performers, and FAQ. Both listed and unlisted published events remain available here by their direct slug. Returns 404 Not Found if the slug doesn’t exist or the event isn’t published.
curl -H "Accept: application/json" \
https://your-subdomain.usetix.io/events/spring-showcase
Query parameters:
| Parameter | Description |
|---|---|
code |
Access code for hidden tickets. Repeat for multiple: ?code=AAA&code=BBB. Without a matching code, hidden tickets are excluded from the response. |
Response: all the event fields, plus:
{
"slug": "spring-showcase",
"title": "Spring Showcase",
"...": "...",
"tickets": [
{
"title": "Early Bird",
"kind": "StandardTicket",
"color": "#10b981",
"features": ["Free coat check"],
"price": { "amount": "15.00", "currency": "EUR" },
"available": true,
"sold_out": false,
"low_stock": false,
"max_per_order": 4
}
],
"performers": [
{
"name": "DJ Example",
"performer_type": "person",
"role": "headliner",
"genre": "Techno",
"starts_at": "2026-05-01T22:00:00Z",
"ends_at": "2026-05-02T01:00:00Z",
"bio": "<p>Berlin-based...</p>",
"image_url": "https://your-subdomain.usetix.io/rails/active_storage/blobs/...",
"links": {
"website": "https://example.com",
"instagram": "https://instagram.com/example",
"spotify": null,
"soundcloud": null,
"tiktok": null,
"youtube": null
}
}
],
"faq_items": [
{ "question": "Is there an age limit?", "answer": "18 and over." }
]
}
Event fields
| Field | Type | Notes |
|---|---|---|
slug |
string | URL slug. Use it as the path parameter on GET /events/:slug. |
title |
string | Event title. |
description |
string | HTML. May contain rich-text formatting. Empty string if no description. |
url |
string | Canonical shop URL for the event. |
starts_at |
string | ISO 8601 UTC. |
doors_open_at |
string | null | ISO 8601 UTC. When guests may enter. null when no separate doors time is set. |
ends_at |
string | ISO 8601 UTC. Operational end time, including the internal five-hour default when no public end was supplied. |
show_end_time |
boolean | Display ends_at publicly only when this is true. |
sales_starts_at |
string | null | ISO 8601 UTC. null if sales are open immediately. |
sales_ends_at |
string | ISO 8601 UTC. |
time_zone |
string | Rails time zone name (e.g. "Berlin"), useful for displaying local times. |
minimum_age |
integer | null | |
video_url |
string | null | YouTube URL if set. |
sold_out |
boolean | true when no tickets remain. |
image_url |
string | null | Absolute URL to the event image. |
background_image_url |
string | null | Absolute URL to the background image. |
gallery_image_urls |
string[] | Absolute URLs to gallery images, in order. |
venue |
object | See below. |
cheapest_available_price |
object | null | The lowest-priced ticket still available. null when sold out or no tickets configured. |
venue
| Field | Type | Notes |
|---|---|---|
name |
string | |
address |
string | Street address. |
postal_code |
string | null | |
city |
string | |
country |
string | Country code or name as stored on the venue. |
latitude |
number | null | Decimal degrees. |
longitude |
number | null | Decimal degrees. |
time_zone |
string | Rails time zone name. |
tickets[] (show only)
| Field | Type | Notes |
|---|---|---|
title |
string | |
kind |
string | "StandardTicket" or "GroupTicket". |
color |
string | Hex color (e.g. "#10b981") used in the shop. |
features |
string[] | Bullet-point list of perks. |
price |
object | { amount, currency }. See Money fields. |
available |
boolean | true when stock and event capacity allow purchase. |
sold_out |
boolean | Inverse of available. |
low_stock |
boolean | true when ≤ 15 left in stock. Stays false for unlimited-stock tickets. |
max_per_order |
integer | null | Per-order purchase cap. null when unlimited. |
performers[] (show only)
| Field | Type | Notes |
|---|---|---|
name |
string | |
performer_type |
string | "person" or "performing_group". |
role |
string | "headliner", "support", or "guest". |
genre |
string | null | |
starts_at |
string | null | Set time, ISO 8601 UTC. |
ends_at |
string | null | |
bio |
string | null | HTML. |
image_url |
string | null | |
links |
object | Social links, website, instagram, spotify, soundcloud, tiktok, youtube. Each is a string or null. |
faq_items[] (show only)
| Field | Type | Notes |
|---|---|---|
question |
string | |
answer |
string | Plain text. |
What’s not in the feed
Deliberately omitted, because it’s either internal or for the authenticated admin API:
- Database IDs (use
slugas the stable identifier) - Drafts and unpublished events
- Unlisted events on the index; a published unlisted event remains available from
GET /events/:slug - Hidden tickets without a matching access code
- Past events (on the index)
- Capacity, sales totals, ticket stock counts (use
low_stockandsold_outinstead) - Ticket access codes
- Customer or order data
Authentication
None. The feed only exposes data your shop is already publishing. No tokens, no headers required beyond Accept: application/json.
CORS
All responses include:
Access-Control-Allow-Origin: *
You can fetch the feed from any origin in the browser without a proxy.
Caching
The feed is built to be cached by browsers, CDNs, and your own infrastructure.
Cache-Control: public, max-age=60: both endpoints. Shared caches (CDN, reverse proxy) can serve the response for up to a minute.ETag:GET /events/:slugreturns an ETag based on the event’s last-modified timestamp. Send it back asIf-None-Matchon subsequent requests; if nothing has changed, you get a304 Not Modifiedwith no body.
A typical embed setup polls every minute or two, well within the cache window, and gets near-instant responses.
Custom domains
If your shop runs on a custom domain (e.g. tickets.yourbrand.com), the feed is available at the same path on that domain:
https://tickets.yourbrand.com/events.json
https://tickets.yourbrand.com/events/spring-showcase.json
Embedding example
A minimal “next event” widget for any HTML page:
<div id="next-event"></div>
<script>
fetch("https://your-subdomain.usetix.io/events.json")
.then(r => r.json())
.then(data => {
const next = data.events[0];
if (!next) return;
document.getElementById("next-event").innerHTML = `
<a href="${next.url}">
<img src="${next.image_url}" alt="">
<h3>${next.title}</h3>
<time datetime="${next.starts_at}">${new Date(next.starts_at).toLocaleString()}</time>
<p>${next.venue.name}, ${next.venue.city}</p>
${next.sold_out ? "<span>Sold out</span>" : `<span>From ${next.cheapest_available_price.currency} ${next.cheapest_available_price.amount}</span>`}
</a>
`;
});
</script>
Changes and stability
The feed is intentionally narrow, additive changes (new fields) may ship without notice. We won’t remove fields without a deprecation window. If something breaks for you, let us know.