TypeScript client for the Luma (lu.ma) public API.
Small, dependency-free, ESM-only. Wraps the endpoints needed to mirror a Luma calendar — events, guests, people, coupons — and exposes typed escape hatches for everything else.
Unofficial. Not affiliated with or endorsed by Luma. "Luma" is a trademark of its respective owner.
bun add @ingram-tech/luma
# or: npm install @ingram-tech/lumaRequires Node.js 18+ (uses the global fetch). A Luma API key is needed —
available under Calendar Settings → API on a Luma Plus plan.
import { LumaClient } from "@ingram-tech/luma"; const luma = new LumaClient({ apiKey: process.env.LUMA_API_KEY! }); // or: const luma = LumaClient.fromEnv(); // reads LUMA_API_KEY // Iterate every event the calendar manages (auto-paginates). The calendar is // inferred from the API key — no calendar id needed. for await (const event of luma.calendar.listEvents()) { console.log(event.id, event.name); } // ...or collect them into an array. const events = await luma.calendar.listAllEvents(); // Guests of an event. const guests = await luma.events.listAllGuests({ eventId }); // A single guest (with order detail), by guest id. const guest = await luma.events.getGuest({ eventId, guestId: "gst-..." }); // Approve a guest. await luma.events.updateGuestStatus({ eventId, guestId: guest.id, status: "approved" }); // Ticket types, including prices (cents / currency). const tiers = await luma.events.listTicketTypes({ eventId }); // Register a guest (host-side — does NOT take payment; Luma owns checkout). await luma.events.addGuests({ eventId, guests: [{ email: "a@b.com", name: "Ada Lovelace" }], ticketTypeId: tiers[0]?.id, }); // Coupons. const coupon = await luma.calendar.createCoupon({ code: "SUMMIT-2027", remainingCount: 1, discount: { type: "amount", centsOff: 4000, currency: "EUR" }, });
Cursor-paginated methods (listEvents, listContacts, listGuests,
listCoupons) return an AsyncGenerator that follows next_cursor
automatically. Each has a listAll... sibling that drains it into an array.
collect() is exported for draining any async iterable.
The typed methods cover the common endpoints. For anything else, call the API
directly — request and paginate are public, and the ResponseOf /
QueryOf / BodyOf helpers type any endpoint straight from the spec:
import type { ResponseOf } from "@ingram-tech/luma"; // `data` is typed as the endpoint's real response — no hand-written type. const data = await luma.request<ResponseOf<"/v1/events/get", "get">>( "/v1/events/get", { query: { event_id: eventId } }, ); // Any cursor-paginated endpoint. for await (const entry of luma.paginate<MyEntry>("/v1/some/list", { foo: "bar" })) { // ... }
request also accepts fetchInit for passing through framework-specific
options, e.g. Next.js cache hints:
await luma.request("/v1/calendars/events/list", { fetchInit: { next: { revalidate: 300 } }, });
Non-2xx responses (and non-JSON bodies) throw LumaApiError, which carries the
status, raw body, and path, plus convenience getters:
import { LumaApiError } from "@ingram-tech/luma"; try { await luma.calendar.createCoupon({ code: "DUP", discount: { type: "percent", percentOff: 10 } }); } catch (err) { if (err instanceof LumaApiError && err.isDuplicateCouponCode) { // coupon already exists — treat as idempotent } }
isAuthError (401/403) and isRateLimited (429) are also provided.
| Area | Methods |
|---|---|
| Calendar events | calendar.listEvents, calendar.listAllEvents |
| Calendar contacts | calendar.listContacts, calendar.listAllContacts |
| Coupons | calendar.listCoupons, calendar.findCouponByCode, calendar.createCoupon |
| Events | events.get |
| Guests | events.listGuests, events.listAllGuests, events.getGuest, events.updateGuestStatus, events.addGuests |
| Ticket types | events.listTicketTypes, events.getTicketType, events.createTicketType, events.updateTicketType, events.deleteTicketType |
| Anything else | request, paginate (typed via ResponseOf/QueryOf/BodyOf) |
Types are generated from Luma's published OpenAPI
(https://public-api.luma.com/openapi.json) — run bun run generate to refresh
src/generated/openapi.ts when the API changes. The convenience methods and
their camelCase option objects are the only hand-written surface.
Note the API has no checkout/payment endpoint — addGuests registers a
guest host-side but never takes payment; ticket purchase happens on Luma's own
hosted checkout.
bun install bun run ci # type-check, lint, test, build bun run generate # regenerate types from Luma's OpenAPI bun run test # watch mode
MIT © Ingram Technologies