# Kashta API

> Read access to the Kashta festival: program, places, vendors, offers, loyalty and members. Base URL: https://kashtaapi.thekashtah.com · OpenAPI: https://kashtaapi.thekashtah.com/openapi.json

The Kashta API gives approved partners read access to the Kashta festival: the program, venues and gates, vendors with their menus and offers, the loyalty program, and (with extra scopes) a member's points, vouchers and bookings. It is built for AI assistants such as **Aloochat** as much as for apps and dashboards.

## Getting a key
Keys are issued only by the Kashta team. Ask your Kashta contact for one and say what you will use it for. A Kashta super admin creates it in the dashboard (**System → API Access**) with the scopes you need and sends it to you once. **Kashta can't show the key again.** If you lose it, we roll it: you get a new one and the old one stops working at once.

Keep the key on your server. Never put it in a mobile app, a web page or a public repository.

## Making a call
Base URL: `https://kashtaapi.thekashtah.com`. Every endpoint is a `GET` that returns JSON (UTF-8).

```bash
curl https://kashtaapi.thekashtah.com/v1/events?date=today \
  -H "Authorization: Bearer kst_your_key"
```

The header `X-API-Key: kst_your_key` works too. Keys in the URL are refused, because URLs end up in logs.

Check your key with `GET /v1/me`.

## Scopes
| Scope | Gives access to |
|---|---|
| `catalog:read` | The festival, events, venues, gates, vendors, menus, offers, rewards, loyalty rules, FAQ, search, and the knowledge export. This is everything a visitor can see in the app. |
| `members:read` | One member's profile summary: tier, points, vouchers and loyalty progress. You can look a member up by id, member number, phone or email. **The response never includes contact details.** |
| `bookings:read` | A member's bookings and tickets, and a booking looked up by its code. |
| `stats:read` | Live operations: visitors today, check-ins, gate waits, and events in progress. |

Calling an endpoint without its scope returns `403 insufficient_scope`.

## Conventions
- **Time:** every time is Kuwait time (`+03:00`), in ISO 8601. A festival **day runs from 04:00 to 04:00**, so a show at 01:30 belongs to the previous day's `date`. Event times already include any delay; `delay_minutes` says by how much.
- **Money:** prices come in two forms. `*_kwd` is a string with 3 decimals (`"2.500"`). `*_fils` is an integer (1 KWD = 1000 fils).
- **Text:** Arabic first. `*_ar` is always set. `*_en` is `null` until an English version exists.
- **Search:** the `q` parameter matches Arabic regardless of أ/إ/آ/ا, ى/ي, ة/ه and diacritics.
- **Links:** `app_url` opens the item in the Kashta app, or on the web for people who don't have the app. Use it as the "Book now" or "Open" button. Bookings and payments only happen in the app.
- **Paging:** paged lists return `meta: {total, limit, offset, next_offset}`. `next_offset` is `null` on the last page.
- **Missing values** are `null`, never left out.

## Errors
Errors use the HTTP status and a JSON body:
```json
{ "error": { "code": "invalid_parameter", "message": "Parameter \"date\" must be a date like 2026-10-14, \"today\" or \"tomorrow\".", "parameter": "date" } }
```
| Status | `code` | Meaning |
|---|---|---|
| 400 | `invalid_parameter` | A parameter is malformed. `parameter` names it. |
| 401 | `invalid_key`, `key_revoked`, `key_expired` | No key, an unknown key, or a key that no longer works |
| 403 | `insufficient_scope`, `ip_not_allowed` | The key can't call this endpoint, or can't be used from this IP |
| 404 | `not_found`, `unknown_route` | No such item, or no such endpoint |
| 405 | `method_not_allowed` | The API is read-only |
| 429 | `rate_limited` | Too many calls this minute. Wait for `Retry-After` seconds. |
| 5xx | `internal`, `upstream`, `unavailable` | Our side. Retry with backoff and quote `X-Request-Id` if it persists. |

## Rate limits
Each key has a per-minute limit, 120 by default. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Ask us if you need a higher limit.

## Using it with Aloochat (or any AI assistant)
1. Give the assistant this spec, `https://kashtaapi.thekashtah.com/openapi.json`. Most tool and action builders import OpenAPI directly. For a plain-text version, use `https://kashtaapi.thekashtah.com/llms.txt`.
2. Set the auth to **Bearer token** with your key, or add the header `Authorization: Bearer kst_…` to each tool.
3. These tools cover most visitor questions:
   - `GET /v1/events/now`: what's on now and next
   - `GET /v1/events?date=today`: today's program
   - `GET /v1/search?q=…`: find anything by name
   - `GET /v1/vendors?open_now=true`: what's open
   - `GET /v1/offers`: live offers
   - `GET /v1/gates`: gate waits
4. For a knowledge base, use `GET /v1/knowledge`. It returns the whole public catalog in one call, with an `etag` that only changes when the content does.
5. For member questions such as "how many points do I have?", Kashta's assistant already identifies the member by their id. With `members:read`, call `GET /v1/members/{id}`. Never ask a member for their phone number or email in chat.
6. Tell members to book in the app with the event's `app_url`. The assistant can't book or take payments.

## Support
Write to your Kashta contact and quote the `X-Request-Id` header of the call in question.

# Endpoints

## Account

Your key.

### GET /v1/me

**Check your key.** The key's name, scopes and rate limit. Use it to test that the key works.

Scope: any valid key

Returns: `{ data: Me }`

## Festival

The festival itself and the app's live status.

### GET /v1/festival

**Get the festival.** Name, dates, time zone, today's festival day and the site location.

Scope: `catalog:read`

Returns: `{ data: Festival }`

### GET /v1/status

**Get live status.** Whether the app is in maintenance, the status banner, how many gates are open and how many events are live.

Scope: `catalog:read`

Returns: `{ data: Status }`

## Events

The program: concerts, experiences and stage shows, with tickets and availability.

### GET /v1/events

**List events.** Published events in start order. With no date filter, returns events that haven't ended yet. Cancelled events are included with `status: cancelled`, so you can tell people.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `date` | query | Festival day, `YYYY-MM-DD`, `today` or `tomorrow`. A day runs 04:00–04:00 Kuwait time. |
| `from` | query | Only events that end after this time (ISO 8601; a bare date means 00:00 Kuwait). |
| `to` | query | Only events that start before this time. |
| `q` | query | Text search in title, subtitle, host, description and tags (Arabic or English). |
| `venue_id` | query | Only events at this venue. |
| `kind` | query | Event kind. One of: `event`, `experience`, `stage`. |
| `tag` | query | Only events with this tag (exact). |
| `featured` | query | Only featured (`true`) or non-featured (`false`) events. |
| `available` | query | `true`: tickets on sale and left. `false`: sold out or not on sale. |
| `status` | query | Schedule status. One of: `on_time`, `delayed`, `cancelled`. |
| `include_past` | query | Include ended events when no date filter is given (default false). |
| `limit` | query | How many to return (1–100, default 50). |
| `offset` | query | How many to skip, for paging (default 0). Use `meta.next_offset` from the previous page. |

Returns: `{ data: Event[], meta: PageMeta }`

### GET /v1/events/now

**What's on now.** Events happening right now (`live`) and the next ones to start (`next`), within `hours`. This is the best single call for "what's on?".

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `limit` | query | How many to return (1–50, default 10). |
| `hours` | query | How far ahead `next` looks (1–168, default 24). |

Returns: `{ data: { now: string | null, live: Event[], next: Event[] } }`

### GET /v1/events/{id}

**Get an event.** One event with its description, organizer, original schedule and every ticket tier with its price and seats left.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Event id (UUID) |

Returns: `{ data: EventDetail }`

## Places

Venues (stages, tents, zones) and entry gates with live waits.

### GET /v1/venues

**List venues.** Stages, tents and zones with their location.

Scope: `catalog:read`

Returns: `{ data: Venue[] }`

### GET /v1/venues/{id}

**Get a venue.** One venue and its next 20 events.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Venue id (UUID) |

Returns: `{ data: Venue + { upcoming_events: Event[] } }`

### GET /v1/gates

**List gates.** Entry gates and parking: open or closed, and the current wait in minutes.

Scope: `catalog:read`

Returns: `{ data: Gate[] }`

## Vendors & offers

Restaurants, cafés, food trucks and shops; their menus and offers.

### GET /v1/vendors

**List vendors.** Active vendors, alphabetically (Arabic). `q` also searches menu items, so "burger" finds every vendor that sells one.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `q` | query | Text search in name, tagline, description, category, zone and menu items. |
| `category` | query | Vendor category. One of: `cafe`, `restaurant`, `fast_food`, `food_truck`, `shop`. |
| `open_now` | query | Only vendors open (`true`) or closed (`false`) right now. |
| `has_offers` | query | Only vendors with (or without) a live offer. |
| `limit` | query | How many to return (1–100, default 50). |
| `offset` | query | How many to skip, for paging (default 0). Use `meta.next_offset` from the previous page. |

Returns: `{ data: Vendor[], meta: PageMeta }`

### GET /v1/vendors/{id}

**Get a vendor.** One vendor with contact details, location, the full available menu and live offers.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Vendor id (UUID) |

Returns: `{ data: VendorDetail }`

### GET /v1/offers

**List live offers.** Approved offers that are running right now, ending soonest first.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `vendor_id` | query | Only this vendor's offers. |
| `kind` | query | Offer type. One of: `bogo`, `percent`, `free`, `gift`. |

Returns: `{ data: Offer[] }`

## Loyalty

How points are earned, the tiers, the stamp programs and the points store.

### GET /v1/loyalty

**Get the loyalty program.** How points are earned, the member tiers with their thresholds and multipliers, and the stamp programs running now.

Scope: `catalog:read`

Returns: `{ data: Loyalty }`

### GET /v1/rewards

**List points-store rewards.** Rewards members can buy with points in the app, while in stock.

Scope: `catalog:read`

Returns: `{ data: Reward[] }`

## Assistant

Search, FAQ and the full knowledge export for AI assistants.

### GET /v1/search

**Search everything.** One query across upcoming events, vendors (and their menus), live offers and venues.

Scope: `catalog:read`

| Parameter | In | Description |
|---|---|---|
| `q` (required) | query | 2–100 characters, Arabic or English. |
| `limit` | query | How many to return (1–25, default 10). |

Returns: `{ data: SearchResult }`

### GET /v1/faq

**List FAQ.** Question and answer pairs and reference documents written by the Kashta team for assistants.

Scope: `catalog:read`

Returns: `{ data: Faq[], documents: { name: string, content: string }[] }`

### GET /v1/knowledge

**Export the knowledge base.** Everything an assistant may know, in one compact document: events with tiers, venues, gates, vendors with menus, offers, loyalty, FAQ and documents. `etag` only changes when the content does, so you can skip re-ingesting. Refresh it every 15 minutes or so.

Scope: `catalog:read`

Returns: `{ data: object }`

## Members

One member's summary, points, vouchers and loyalty progress (`members:read`).

### GET /v1/members/lookup

**Find a member.** Find one member by phone (the last 8 digits must match), email (case-insensitive) or member number. Give exactly one of them. The response never includes contact details.

Scope: `members:read`

| Parameter | In | Description |
|---|---|---|
| `phone` | query | Phone number in any format, e.g. `+965 5000 1234`. |
| `email` | query | Email address. |
| `member_number` | query | 8-digit member number, with or without `KSH` and spaces. |

Returns: `{ data: Member }`

### GET /v1/members/{id}

**Get a member.** Tier, points and progress to the next tier.

Scope: `members:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Member id (UUID) or member number (`36894178`, `KSH 3689 4178`) |

Returns: `{ data: Member }`

### GET /v1/members/{id}/vouchers

**List a member's vouchers.** Vouchers in the member's wallet, newest first (up to 100). The redemption code is never returned: the member shows it from the app.

Scope: `members:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Member id or member number |
| `state` | query | Which vouchers (default `active`). One of: `active`, `used`, `expired`, `all`. |

Returns: `{ data: Voucher[] }`

### GET /v1/members/{id}/points

**List a member's points history.** The points ledger, newest first.

Scope: `members:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Member id or member number |
| `limit` | query | How many to return (1–100, default 20). |
| `offset` | query | How many to skip, for paging (default 0). Use `meta.next_offset` from the previous page. |

Returns: `{ data: PointsEntry[], meta: PageMeta }`

### GET /v1/members/{id}/programs

**List a member's loyalty progress.** Progress on every stamp program running now, including programs not started yet.

Scope: `members:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Member id or member number |

Returns: `{ data: ProgramProgress[] }`

## Bookings

A member's bookings and tickets (`bookings:read`).

### GET /v1/members/{id}/bookings

**List a member's bookings.** Bookings with their event and tickets, newest first.

Scope: `bookings:read`

| Parameter | In | Description |
|---|---|---|
| `id` (required) | path | Member id or member number |
| `upcoming` | query | `true`: only pending or confirmed bookings for events that haven't ended. |
| `limit` | query | How many to return (1–100, default 20). |
| `offset` | query | How many to skip, for paging (default 0). Use `meta.next_offset` from the previous page. |

Returns: `{ data: Booking[], meta: PageMeta }`

### GET /v1/bookings/{code}

**Get a booking by code.** One booking by the reference the member sees (e.g. `KSH-M53YVT`), with the member's id, member number and first name.

Scope: `bookings:read`

| Parameter | In | Description |
|---|---|---|
| `code` (required) | path | Booking code |

Returns: `{ data: Booking + { member: object } }`

## Stats

Live operations numbers (`stats:read`).

### GET /v1/stats/live

**Get live stats.** Today's festival day: visitors, check-ins by type, members on site now (from app location), gate waits and live events with attendance.

Scope: `stats:read`

Returns: `{ data: LiveStats }`

# Objects

## PageMeta
- `total`: integer
- `limit`: integer
- `offset`: integer
- `next_offset`: integer | null. Offset of the next page, null on the last page

## Me
- `id`: string
- `name`: string
- `partner`: string | null
- `prefix`: string. First characters of the key
- `scopes`: string[]
- `rate_per_minute`: integer
- `expires_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `created_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `ip_restricted`: boolean

## Point
- `lat`: number
- `lng`: number

## Festival
- `name_ar`: string
- `name_en`: string | null
- `starts_on`: string | null. YYYY-MM-DD
- `ends_on`: string | null. YYYY-MM-DD
- `timezone`: string
- `utc_offset`: string
- `currency`: string
- `business_day_starts_at`: string. A festival day runs from this time to the same time next day
- `today`: string. Current festival day
- `now`: string | null. Server time
- `location`: Point
- `app_domain`: string

## Status
- `maintenance`: boolean. The app is in maintenance
- `maintenance_title_ar`: string | null
- `maintenance_body_ar`: string | null
- `banner`: { tone: string, text_ar: string | null, text_en: string | null }
- `gates_open`: integer
- `events_live`: integer

## VenueRef
- `id`: string
- `name_ar`: string
- `name_en`: string | null
- `zone_ar`: string | null

## VendorRef
- `id`: string
- `name_ar`: string
- `name_en`: string | null
- `zone_ar`: string | null
- `stall`: string | null

## Booking_Summary
- `required`: boolean. Tickets (even free ones) are needed; false = open entry
- `sales_open`: boolean. Booking is possible in the app right now
- `free`: boolean. Every tier is free
- `price_from_fils`: integer | null. Lowest tier price in fils (1 KWD = 1000 fils)
- `price_from_kwd`: string | null. Lowest tier price in Kuwaiti dinar, 3 decimals
- `remaining`: integer | null. Seats left over all tiers
- `sold_out`: boolean

## Event
- `id`: string
- `kind`: "event" | "experience" | "stage"
- `title_ar`: string
- `title_en`: string | null
- `subtitle_ar`: string | null
- `host_ar`: string | null
- `host_role_ar`: string | null
- `starts_at`: string | null. Start, Kuwait time, delay included
- `ends_at`: string | null. End, Kuwait time, delay included
- `date`: string. Festival day (04:00–04:00)
- `status`: "on_time" | "delayed" | "cancelled"
- `delay_minutes`: integer
- `state`: "upcoming" | "live" | "ended" | "cancelled". Where the event is right now
- `venue`: VenueRef
- `tags`: string[]
- `featured`: boolean
- `color`: string | null
- `image_url`: string | null. Banner image
- `booking`: Booking_Summary
- `app_url`: string. Opens the event in the app

## Tier
- `id`: string
- `name_ar`: string
- `detail_ar`: string | null
- `price_fils`: integer | null. Price in fils (1 KWD = 1000 fils)
- `price_kwd`: string | null. Price in Kuwaiti dinar, 3 decimals
- `remaining`: integer
- `sold_out`: boolean

## EventDetail (everything in Event, plus)
- `about_ar`: string | null
- `scheduled_starts_at`: string | null. Original start before any delay
- `scheduled_ends_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `capacity`: integer | null
- `organizer`: { id: string, name_ar: string, name_en: string | null }
- `tiers`: Tier[]
- `external_url`: string | null
- `updated_at`: string | null. Kuwait time, ISO 8601 with +03:00

## Venue
- `id`: string
- `name_ar`: string
- `name_en`: string | null
- `zone_ar`: string | null
- `color`: string | null
- `capacity`: integer | null
- `location`: Point

## Gate
- `id`: string
- `name_ar`: string
- `name_en`: string | null
- `detail_ar`: string | null
- `color`: string | null
- `is_open`: boolean
- `wait_minutes`: integer | null. Current wait
- `location`: Point
- `updated_at`: string | null. Kuwait time, ISO 8601 with +03:00

## Vendor
- `id`: string
- `name_ar`: string
- `name_en`: string | null
- `category`: "cafe" | "restaurant" | "fast_food" | "food_truck" | "shop"
- `tagline_ar`: string | null
- `zone_ar`: string | null
- `stall`: string | null
- `hours`: { opens: string | null, closes: string | null, open_now: boolean | null }
- `orders_paused`: boolean. Temporarily not taking orders
- `rating`: number | null
- `reviews`: integer | null
- `logo_url`: string | null
- `cover_url`: string | null
- `brand_color`: string | null
- `live_offers`: integer
- `app_url`: string

## MenuItem
- `id`: string
- `name_ar`: string
- `detail_ar`: string | null
- `price_fils`: integer | null. Price in fils (1 KWD = 1000 fils)
- `price_kwd`: string | null. Price in Kuwaiti dinar, 3 decimals
- `popular`: boolean
- `image_url`: string | null

## VendorDetail (everything in Vendor, plus)
- `about_ar`: string | null
- `phone`: string | null
- `instagram`: string | null
- `menu_url`: string | null
- `location`: Point
- `menu`: MenuItem[]
- `offers`: Offer[]

## Offer
- `id`: string
- `vendor`: VendorRef
- `kind`: "bogo" | "percent" | "free" | "gift"
- `title_ar`: string
- `detail_ar`: string | null
- `badge_ar`: string | null
- `percent`: integer | null
- `starts_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `ends_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `image_url`: string | null
- `app_url`: string

## Reward
- `id`: string
- `title_ar`: string
- `title_en`: string | null
- `value_ar`: string | null
- `value_en`: string | null
- `cost_points`: integer
- `vendor`: VendorRef
- `stock_left`: integer | null. null = unlimited
- `valid_days`: integer | null. Days the voucher stays valid
- `image_url`: string | null

## LoyaltyTier
- `key`: "sand" | "dunes" | "oasis" | "black"
- `name`: string. Display name
- `min_lifetime_points`: integer | null. Lifetime points to reach it; null = by invitation only
- `multiplier`: number. Points multiplier on purchases
- `by_invitation`: boolean

## Program
- `id`: string
- `title_ar`: string
- `title_en`: string | null
- `detail_ar`: string | null
- `rule_kind`: "stamps" | "spend" | "steps" | "visits" | "events"
- `goal`: integer
- `reward_ar`: string | null
- `reward_en`: string | null
- `reward_kind`: string | null
- `reward_points`: integer | null
- `vendor`: VendorRef
- `starts_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `ends_at`: string | null. Kuwait time, ISO 8601 with +03:00

## Loyalty
- `earning`: { points_per_kwd: number, welcome_points: integer, checkin_points: integer, booking_points_per_ticket: integer }
- `tiers`: LoyaltyTier[]
- `programs`: Program[]

## Faq
- `q_ar`: string
- `a_ar`: string
- `q_en`: string | null
- `a_en`: string | null

## SearchResult
- `query`: string
- `events`: Event[]
- `vendors`: Vendor[]
- `offers`: Offer[]
- `venues`: Venue[]

## Member
- `id`: string
- `member_number`: string
- `member_number_display`: string
- `full_name`: string | null
- `first_name`: string | null
- `language`: "ar" | "en"
- `status`: "pending_profile" | "active" | "banned"
- `tier`: { key: string, name: string }
- `next_tier`: { key: string, name: string, points_needed: integer }
- `points_balance`: integer
- `lifetime_points`: integer
- `visits`: integer. Festival days checked in
- `registered_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `last_seen_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `app_url`: string. Opens the member's pass in the app

## Voucher
- `id`: string
- `title_ar`: string
- `title_en`: string | null
- `value_ar`: string | null
- `value_en`: string | null
- `vendor`: VendorRef
- `state`: "active" | "used" | "expired" | "void"
- `source`: string
- `issued_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `expires_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `used_at`: string | null. Kuwait time, ISO 8601 with +03:00

## PointsEntry
- `id`: string
- `delta`: integer. Points added (+) or spent (−)
- `reason`: "welcome" | "booking" | "check_in" | "shop_order" | "redeem" | "admin_adjust" | "referral" | "steps" | "reversal" | "expiry"
- `balance_after`: integer | null
- `at`: string | null. Kuwait time, ISO 8601 with +03:00

## ProgramProgress
- `program`: { id: string, title_ar: string, title_en: string | null, rule_kind: string, reward_ar: string | null, vendor: VendorRef }
- `goal`: integer
- `progress`: integer
- `remaining`: integer
- `completed_count`: integer
- `last_completed_at`: string | null. Kuwait time, ISO 8601 with +03:00

## Ticket
- `id`: string
- `tier_ar`: string | null
- `seat_no`: integer | null
- `status`: "valid" | "used" | "void"
- `used_at`: string | null. Kuwait time, ISO 8601 with +03:00

## Booking
- `id`: string
- `code`: string
- `status`: "pending" | "confirmed" | "cancelled" | "refunded"
- `source`: string
- `quantity`: integer
- `total_fils`: integer | null. Total in fils (1 KWD = 1000 fils)
- `total_kwd`: string | null. Total in Kuwaiti dinar, 3 decimals
- `created_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `cancelled_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `refunded_at`: string | null. Kuwait time, ISO 8601 with +03:00
- `event`: Event | null
- `tickets`: Ticket[]

## LiveStats
- `now`: string | null. Kuwait time, ISO 8601 with +03:00
- `business_date`: string
- `visitors_today`: integer. Members who checked in at a gate today
- `on_site_now`: integer. Members inside the site now (app location)
- `check_ins_today`: { gate: integer, door: integer, self: integer }
- `gates`: { id: string, name_ar: string, is_open: boolean, wait_minutes: integer | null }[]
- `events_live`: { id: string, title_ar: string, venue: VenueRef, checked_in: integer, capacity: integer | null, tickets_sold: integer }[]
