@ingram-tech/luma
v0.3.0
Published
TypeScript client for the Luma (lu.ma) public API.
Maintainers
Readme
@ingram-tech/luma
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.
Install
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.
Usage
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: "[email protected]", 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" },
});Pagination
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.
Escape hatches
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 } },
});Errors
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.
API coverage
| 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.
Development
bun install
bun run ci # type-check, lint, test, build
bun run generate # regenerate types from Luma's OpenAPI
bun run test # watch modeLicense
MIT © Ingram Technologies
