Desi-Clans/docs/API_SPEC.md
2026-10-08 02:52:58 +05:30

13 KiB

API Specification — Desi Clans

Version: 1.0
Base URL: https://api.desiclans.com (prod) / http://localhost:8000 (dev)
Format: JSON unless noted
Auth: Authorization: Bearer <access_token> for protected routes


1. Conventions

Item Rule
Errors { "detail": "message" } or validation array from FastAPI
Dates ISO 8601 UTC in JSON
Pagination ?limit=20&offset=0 where added later; v1 lists unbounded with sane caps in service
IDs Integer primary keys in v1
Slugs Lowercase, hyphenated, unique per edition

2. Platform — /api

GET /health

Liveness and PostgreSQL connectivity check.

Response 200 — database reachable

{
  "status": "ok",
  "service": "desi-clans-api",
  "database": "connected"
}

Response 503 — database unreachable

{
  "status": "degraded",
  "service": "desi-clans-api",
  "database": "unavailable"
}

GET /events

List catalog events (published by default).

Query

Param Type Default Description
category holi | garba | dandiya | bhajan_clubbing | special — Filter by category
status draft | published | cancelled published Filter by status
upcoming boolean true If true, only starts_at >= now (UTC)

Response 200 — EventSummary[]

[
  {
    "id": 1,
    "slug": "rang-barse-palace-grounds-2026",
    "title": "Rang Barse — Palace Grounds",
    "category": "holi",
    "status": "published",
    "starts_at": "2026-03-09T10:00:00Z",
    "ends_at": "2026-03-09T18:00:00Z",
    "is_featured": true,
    "cover_image_url": "https://…",
    "cover_image_alt": "…",
    "venue": {
      "id": 1,
      "slug": "palace-grounds",
      "name": "Palace Grounds",
      "area": "Vasanth Nagar",
      "city": "Bengaluru",
      "address": "…",
      "map_url": "https://…",
      "description": "…"
    }
  }
]

GET /events/{slug}

Response 200 — EventDetail (includes description, meta_description, media[], ticket_types[])

Response 404 — unknown slug


GET /events/{id}/tickets

List purchasable ticket types for a published event (numeric event id).

Response 200 — EventTicketTypePublic[]

[
  {
    "id": 1,
    "event_id": 1,
    "name": "General admission",
    "description": "Full-day access",
    "price_paise": 149900,
    "status": "active",
    "sort_order": 0,
    "remaining_quantity": 42
  }
]

Response 404 — unknown or unpublished event


GET /venues

Response 200 — VenuePublic[]


GET /venues/{id}

Response 200 — VenuePublic
Response 404 — unknown id


3. Public — /api/public (legacy, planned)

Legacy public routes will mount under this prefix when business routers are enabled.


GET /config (not yet mounted)

Site-wide config: platform links, feature flags (future).

Response 200 — SiteConfigPublic

{
  "site_name": "Desi Clans",
  "city": "Bengaluru",
  "links": [
    { "id": 1, "kind": "instagram", "label": "Instagram", "url": "https://...", "sort_order": 0 }
  ]
}

GET /editions

List published editions.

Query

Param Type Description
event_type string Optional enum filter
featured bool Optional

Response 200 — EventEditionPublic[]


GET /editions/{slug}

Single published edition.

Response 200 — EventEditionPublic
Response 404 — not found or unpublished

EventEditionPublic (shape)

{
  "id": 1,
  "title": "Holi at Palace Grounds",
  "slug": "holi-palace-grounds-2026",
  "event_type": "holi",
  "city": "Bengaluru",
  "venue_name": "Palace Grounds",
  "event_date": "2026-03-10T10:00:00Z",
  "description": "...",
  "is_featured": true,
  "cover_image_url": "/media/...",
  "cover_thumb_url": "/media/.../thumb",
  "tickets": {
    "bookmyshow": "https://...",
    "district": null,
    "instagram": null
  },
  "meta_description": "...",
  "media": [
    { "id": 1, "url": "...", "thumb_url": "...", "caption": null, "sort_order": 0 }
  ]
}

Future: ticketing_mode, tiers[] when native ticketing is enabled.


GET /go

Outbound redirect with click tracking.

Query

Param Required Description
kind yes e.g. bookmyshow, district, instagram
edition_id no Edition context

Response 302 → destination URL


POST /subscribe

Body — SubscribeRequest

{ "email": "user@example.com", "neighborhood": "HSR Layout" }

Response 201 — created
Response 409 — email already subscribed


GET /editions/{slug}/calendar.ics

Response 200 — text/calendar body


3. Share (crawler HTML)

GET /share/events/{slug}

Also available under public router duplicate path per deployment.

Response 200 — HTML with Open Graph tags (no auth).


4. Auth — /api/auth

Attendee accounts (bcrypt-hashed passwords, JWT bearer tokens). Admin auth remains separate under /api/admin when legacy routers are mounted.

Method Path Auth Description
POST /register — Create account
POST /login — Issue access token
GET /me Bearer Current user profile

POST /register

Body — RegisterRequest

{
  "email": "guest@example.com",
  "password": "minimum-8-chars",
  "full_name": "Optional Name"
}

Response 201 — Token

Response 409 — email already registered
Response 422 — validation error (password < 8 characters, invalid email)


POST /login

Body — LoginRequest

{ "email": "guest@example.com", "password": "********" }

Response 200 — Token

{ "access_token": "...", "token_type": "bearer" }

Response 401 — invalid credentials

JWT claims: sub (user id), exp, type: "user". Lifetime from ACCESS_TOKEN_EXPIRE_MINUTES (server env).


GET /me

Headers — Authorization: Bearer <access_token>

Response 200 — UserPublic

{
  "id": 1,
  "email": "guest@example.com",
  "full_name": "Optional Name",
  "is_active": true,
  "created_at": "2026-04-04T12:00:00Z"
}

Response 401 — missing, invalid, or expired token

Protected routes use the CurrentUser dependency (get_current_user in app/core/deps.py).


5. Orders & tickets — /api/orders, /api/tickets

Requires attendee Bearer token. Prices are always computed server-side from ticket_types.price_paise; the client sends only ticket_type_id and quantity.

Checkout reserves inventory while status: pending. Tickets are issued only after verified payment (checkout signature or webhook). Never trust client-side “payment success” flags.

Provider: Razorpay in production (PAYMENT_PROVIDER=razorpay). Local dev uses mock (no secrets in React).

Optional header: Idempotency-Key on POST /orders (unique per checkout attempt).

POST /orders

Body — OrderCreate

{
  "event_id": 1,
  "items": [{ "ticket_type_id": 2, "quantity": 2 }]
}

Response 201 — OrderCheckoutResponse (status: pending, payment block with public key_id, provider_order_id, amount_paise, expires_at)

Response 400 — invalid event, wrong event/tier pairing, empty cart
Response 401 — not authenticated
Response 409 — insufficient inventory


POST /orders/{id}/payment/verify

Server-side verification of Razorpay (or mock) checkout signature. Marks order paid and issues tickets.

Body — PaymentVerifyRequest

{
  "provider_order_id": "order_…",
  "provider_payment_id": "pay_…",
  "signature": "…"
}

Response 200 — OrderPublic (status: paid)
Response 400 — invalid signature or amount mismatch
Response 409 — order not pending


POST /orders/{id}/cancel

User abandoned checkout. Releases reserved inventory. Pending orders only.

Response 200 — OrderPublic (status: cancelled)


POST /orders/{id}/payment/mock-complete

Development only (ENVIRONMENT=development, PAYMENT_PROVIDER=mock). Server completes mock payment without client-held secrets.


Webhooks — /api/webhooks

Method Path Auth Description
POST /razorpay X-Razorpay-Signature Razorpay events (payment.captured, payment.failed)
POST /mock X-Mock-Signature HMAC body signature (tests / local)

Webhook processing is idempotent (payment_webhook_events.provider_event_id unique). Duplicate deliveries return { "status": "duplicate_ignored" }.

Env (server only): RAZORPAY_KEY_SECRET, RAZORPAY_WEBHOOK_SECRET — never expose to the frontend. RAZORPAY_KEY_ID is public and returned in checkout payloads for Razorpay.js.

Pending orders expire after ORDER_PAYMENT_TIMEOUT_MINUTES (inventory released, status: failed).


GET /orders/{id}

Response 200 — OrderPublic (owner only)
Response 404 — not found or not your order


GET /tickets/my

Response 200 — TicketPublic[] for the current user


GET /tickets/{id}

Authenticated owner only. Returns full digital pass payload including server-generated QR PNG (base64).

Response 200 — TicketDetailPublic (event, venue, attendee, ticket type, code, qr_payload, qr_image_base64, qr_token)
Response 404 — not found or not your ticket


POST /tickets/verify

Gate / check-in verification (server-side). Marks ticket used atomically — QR reuse rejected.

Headers: X-Check-In-Key (required in production via TICKET_VERIFY_API_KEY)

Body — TicketVerifyRequest

{ "qr_token": "…" }

Response 200 — TicketVerifyResponse (valid, status, message, ticket_code, event_title)


TicketPublic.status / TicketDetailPublic.status: valid | used | cancelled


6. Admin — /api/admin

Requires admin JWT. editor vs super_admin enforced per endpoint.

GET /me

Current admin profile.

Response 200 — AdminUserPublic


Method Path Role Description
GET /links editor+ List active links
POST /links super_admin Create
PATCH /links/{link_id} super_admin Update
DELETE /links/{link_id} super_admin Delete

Editions

Method Path Role Description
GET /editions editor+ All editions (incl. draft)
POST /editions editor+ Create
PATCH /editions/{edition_id} editor+ Update fields, publish flags
POST /editions/{edition_id}/cover editor+ Multipart cover upload
POST /editions/{edition_id}/media editor+ Gallery upload
PATCH /media/{media_id} editor+ Caption, publish flag
POST /editions/{edition_id}/media/reorder editor+ Body: ordered media ids
DELETE /media/{media_id} editor+ Remove asset

EventEditionAdmin extends public fields with is_published, sort_order, raw URLs, etc.


Users & subscribers

Method Path Role Description
GET /users super_admin List admins
POST /users super_admin Create admin
GET /subscribers editor+ Newsletter list

7. Analytics — /api/admin/analytics

GET /clicks/summary

Response 200 — ClickSummaryItem[]

[{ "link_kind": "bookmyshow", "count": 42 }]

GET /clicks/recent

Response 200 — OutboundClickPublic[]


8. Future — Check-in & enhancements

Catalog — /api/public/editions/{slug}/tiers

Response — active tiers with remaining_quantity (or sold out flag).

Orders (extensions)

Method Path Auth Description
POST / User Create pending order
GET / User My orders
GET /{order_id} User Order detail + tickets
POST /webhooks/payment HMAC Provider callback (no JWT)

Check-in — /api/check-in

Method Path Auth Description
POST /scan Staff Body: { "qr": "..." } → ticket status

8. Enums

EventType

holi | garba | dandiya | bhajan_clubbing | special

PlatformKind

bookmyshow | district | instagram | highape | youtube | whatsapp | website | other

AdminRole

super_admin | editor

OrderStatus

pending | paid | cancelled | failed

TicketStatus (issued ticket)

valid | used | cancelled


9. CORS

Allowed origins from CORS_ORIGINS env (comma-separated). Credentials enabled for admin SPA origin.


10. Static media

  • Local dev: GET /media/{path}
  • Prod: CDN URL built from stored key in file_path

11. Versioning

Breaking changes require /api/v2 prefix or negotiated migration. v1 remains unversioned path until scale demands it.