Core
COMPACT, LARGE, MOTORBIKE, EV_CHARGING, DISABLED.Nice-to-have (common in real deployments)
ParkingLot(lotId, name, address, timezone)Floor(floorId, lotId, level)Spot(spotId, floorId, type, status: AVAILABLE|OCCUPIED|OUT_OF_SERVICE|RESERVED, zone)Ticket(ticketId, lotId, vehicle: {plate?, type}, entryGateId, entryAt, assignedSpotId, status: ACTIVE|CLOSED|LOST)Payment(paymentId, ticketId, amount, currency, method, status, providerRef)PricingRule(ruleId, lotId, version, definition)POST /v1/tickets/entry
{
"lotId": "L1",
"gateId": "GATE_E_01",
"vehicle": { "type": "CAR", "plate": "SGP1234A" },
"preferences": { "needEvCharging": false, "needDisabled": false }
}
Response:
{
"ticketId": "T_9f...",
"issuedAt": "2026-02-24T10:01:22+08:00",
"spot": { "spotId": "S_2_041", "floorId": "F2", "zone": "B" },
"barcode": "....",
"status": "ACTIVE"
}
GET /v1/tickets/{ticketId}/quote
Response:
{
"ticketId": "T_9f...",
"entryAt": "...",
"exitAt": "...",
"amount": 6.50,
"currency": "SGD",
"breakdown": [{"rule":"weekday_hourly","amount":6.50}]
}
POST /v1/payments
{
"ticketId": "T_9f...",
"method": "CARD",
"paymentToken": "tok_xxx",
"idempotencyKey": "uuid-unique"
}
POST /v1/tickets/{ticketId}/exit
{
"gateId": "GATE_X_02",
"paidPaymentId": "P_12...",
"vehiclePlate": "SGP1234A"
}
GET /v1/lots/{lotId}/availability?spotType=COMPACT
Response:
{ "lotId":"L1", "counts": { "COMPACT": 120, "EV_CHARGING": 8 } }
/v1/spots/{spotId} (status, type)/v1/pricingRules/publish (new version)/v1/reports/occupancy?from=...&to=...This is a classic edge + central system:
TicketCreated, SpotOccupied, PaymentCompleted, TicketClosedEntry
Ticket Service.Exit
Schema (simplified)
spots(spot_id, lot_id, floor_id, type, status, zone, updated_at)spot_reservations(spot_id, ticket_id, reserved_at, expires_at, state)tickets(ticket_id, lot_id, spot_id, status, entry_at, exit_at, vehicle_plate_hash, ...)Algorithm
UPDATE spots SET status='OCCUPIED' WHERE spot_id=? AND status='AVAILABLE'Trade-offs
Maintain zone_availability(lot_id, zone, type, available_count) updated transactionally.
Allocation:
This reduces lock contention.
If you want “soft reservation” (issue ticket but car hasn’t reached spot), use RESERVED state with TTL; if not occupied within N minutes, release.
State machine
ACTIVE → (CLOSED | LOST)Idempotency
POST /tickets/entry with idempotencyKey from gate.Plate handling
Why: pricing changes constantly; don’t hardcode.
Rule model
Trade-offs
Key concerns
idempotencyKey per ticket payment attempt.INITIATED -> AUTHORIZED -> CAPTURED/FAILED -> REFUNDEDExit gate behavior
Responsibilities
Conflict handling
Metrics
Logs
ticketId, gateId, requestIdEvents