๐๏ธ SeatMatrix
Event ticketing system with seat maps, tier pricing, and intentional QA bugs for testing.
Overview
SeatMatrix is an event ticketing API that lets users browse an event, view a live seat map, reserve seats by ID, and apply promo codes. The application is designed with intentional bugs for QA practice โ including a promo code stacking vulnerability and a concurrency race condition.
/api/ticket
x-api-key: <your-key>
/seatmatrix.html
Quick Start
curl https://qacloud.dev/api/ticket/seats \ -H "x-api-key: YOUR_KEY"
curl -X POST https://qacloud.dev/api/ticket/reserve \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"seats": ["A1", "A2"]}'
Seat Map
The venue has 8 rows (AโH) ร 10 columns = 80 seats total.
- Rows AโB: VIP ($150/seat)
- Rows CโE: GENERAL ($80/seat)
- Rows FโH: BALCONY ($40/seat)
Seat IDs use letter + number format: A1, B7, H10.
Pre-Seeded Reserved Seats
These seats are reserved on every server start: A3 B6 D2 D9 E5 F1 G8 H4
Tiers & Pricing
Booking Flow
The booking flow is client-side: get the seat map, calculate the subtotal from selected seats, optionally apply a promo code, then POST /reserve. The API only validates seat availability at reservation time โ there is no cart or session concept.
Conflict Handling
If any seat in the seats array is already reserved, the API returns 409 Conflict with a conflicts array listing the problematic seat IDs and a booked array for any that succeeded before the conflict was detected.
Promo Codes
The only valid promo code is QA-DISCOUNT, which applies a 10% discount.
POST /api/ticket/promo
{
"code": "QA-DISCOUNT",
"subtotal": 230
}
โ { "valid": true, "code": "QA-DISCOUNT", "discount": 23, "percent": 10 }
Known Bugs (QA Surfaces)
API Reference
Returns the event list. Currently one event: QA Summit 2026 โ Live Testing Spectacular at TechArena, Silicon Bay on June 15, 2026.
{
"events": [{
"id": "evt-001",
"name": "QA Summit 2026 โ Live Testing Spectacular",
"venue": "TechArena, Silicon Bay",
"date": "2026-06-15T19:00:00Z",
"doors_open": "2026-06-15T18:00:00Z"
}]
}
Returns all 80 seats with their tier, price, and current available or reserved status. Also returns tier metadata for UI rendering (color, price, rows).
{
"rows": ["A","B","C","D","E","F","G","H"],
"cols": 10,
"seats": [
{ "id": "A1", "row": "A", "col": 1, "tier": "VIP", "price": 150, "status": "available" },
...
],
"tiers": {
"VIP": { "price": 150, "color": "#f59e0b", "rows": ["A","B"] },
"GENERAL": { "price": 80, "color": "#3b82f6", "rows": ["C","D","E"] },
"BALCONY": { "price": 40, "color": "#8b5cf6", "rows": ["F","G","H"] }
}
}
| Field | Required | Description |
|---|---|---|
seats | required | Array of seat ID strings e.g. ["A1", "B3"] |
Success: { success: true, booked: ["A1","B3"] }
Conflict: 409 { conflicts: ["A3"], booked: ["B3"] }
Unknown seat IDs are treated as conflicts. C7 (ghost seat) will succeed via API even though the UI blocks it.
| Field | Required | Description |
|---|---|---|
code | required | Promo code string. Valid: QA-DISCOUNT |
subtotal | optional | Current total. Used to calculate discount amount. |
Valid: { valid: true, code: "QA-DISCOUNT", discount: 23, percent: 10 }
Invalid: 404 { valid: false, error: "Invalid promo code" }
Test Cases
Steps: GET /seats โ count seats in response: expect exactly 80. Verify rows A,B are VIP; C,D,E are GENERAL; F,G,H are BALCONY. Verify prices match tier definitions.
Steps: GET /seats โ find A3, B6, D2, D9, E5, F1, G8, H4 โ all should have status "reserved".
Steps: POST /reserve with seats: ["A1", "A2"] โ expect 200 with booked: ["A1","A2"]. GET /seats โ verify A1, A2 now show status "reserved".
Steps: POST /reserve with seats: ["A3"] (pre-seeded reserved) โ expect 409 Conflict with conflicts: ["A3"].
Steps: GET /seats โ confirm C7 shows status "available". POST /reserve with seats: ["C7"] โ expect 200. This verifies the API/UI mismatch (UI blocks clicks on C7).
Steps: POST /promo with code: "QA-DISCOUNT", subtotal: 300 โ expect discount: 30, percent: 10.
Steps: POST /promo with code: "DOESNOTEXIST" โ expect 404 with valid: false.
Steps: POST /reserve with seats: [] โ expect 400 Bad Request.
Steps: POST /reserve with seats: ["Z99"] (non-existent seat) โ expect 409 with conflicts: ["Z99"].