# CowTicket — integration guide for AI agents You are integrating an event website (the "storefront") with CowTicket, a headless ticketing backend. The storefront owns all event content (dates, venue, descriptions, design). CowTicket owns commerce: ticket availability, orders, Stripe payment, ticket issuance and check-in. Base URL: https://api.cow-ticket.dev All prices/amounts are in satang (Thai Baht × 100). Currency is THB only. ## Credentials (ask the user for these — never invent them) 1. accountId — the user's CowTicket account id. 2. An API key — created in the CowTicket app under API Keys (shown once at creation). Keep both server-side only (environment variables). Never expose the API key or the JWT below to browser code; proxy CowTicket calls through the storefront's backend. ## Authentication Exchange the API key for a 1-day JWT, cache it, refresh on 401: POST /accounts/token header api-key: body { "accountId": "", "type": "customer" } → 200 { "token": "", "exp": "..." } Send it on every other request: authorization: Bearer ## Endpoint reference ### List what's on sale GET /events/ticketConfigs?eventCode= → { "data": [ { "code", "name", "group", "price", "currency", "available", "limitPerOrder", "startSellingDate", "endSellingDate" } ], "refundPolicy": { "allowed": bool, "termEn": str, "termTh": str } } Only ACTIVE ticket types inside their selling window are returned. "available" is a live count. Show refundPolicy.termEn / termTh near the checkout button (pick by the storefront's language; show both if bilingual). "data" arrives in the organizer's chosen display order — render it in this order, do not re-sort. "group" is an optional section label (string or null) the organizer uses to split ticket options into sections, e.g. sessions or days of the event ("Day 1 (Sat 14 Mar)"). Rendering rule: iterate in order and start a new titled section whenever "group" changes; items with group null are ungrouped — render them flat, before any grouped section, with no heading. If every item's group is null, render one flat list (most events). The label is display text only — items in the same group have no shared behavior, and buyers can still mix items across groups in one order. ### Create an order POST /orders { "accountId": "", "eventCode": "", "email": "", "items": [ { "code": "", "quantity": n } ], // 1–20 items "metadata": { ...any buyer form answers... }, // optional "successUrl": "", "cancelUrl": "" } → { "orderId", "orderNo", "status", "expiresAt", "checkoutUrl" } - status PENDING + checkoutUrl → redirect the buyer to checkoutUrl (Stripe Checkout). - Free orders return status PAID and checkoutUrl null → go straight to the success page. - Inventory is reserved atomically; unpaid orders expire after 1 hour and release seats. - metadata keys/values appear in the organizer's participant exports — put attendee form answers (phone, shirt size, ...) here. - Include the orderId in successUrl (e.g. ?orderId=...) so the success page can poll. ### Check an order / fetch tickets GET /orders/detail?orderId= → { "status": "PENDING|PAID|REFUNDED|EXPIRED|CANCELED", "items", "payment", "tickets": [ { "id", "ticketNo", "status", ... } ], "refundPolicy", ... } GET /tickets/getIdsByOrder?orderId= → ticket ids for the order GET /tickets/detail?ticketId= → one ticket (status, holder, type) ### Check-in (only if building a scanner/gate feature) POST /tickets/redeem { "ticketId": "", "gate": "