Domains
Events
Fetch event listings, event details, and submit RSVPs (attendance) against the uidu Events API.
- Use it for: Public website · Server integration
- Reads: public token — works in the browser
- Writes: Bearer (
apiKey) — server-side only - CLI:
uidu events,uidu attendances - Auth: what each token can do and where it may run — Authentication
Events are what your workspace runs in time — a workshop, a gala, a weekly class. You list them and show their details with the public token, and record RSVPs (attendances) from your server.
Mental model
Event— the metadata:name,description,body,cover,published,publicPath/publicUrl,isPaidEvent,hasSeats,currentCapacity, aprimaryAddress(formattedAddress,name,city,postalCode,countryCode,latitude,longitude), and ready-made calendar links:toGoogleCalendarUrlandtoIcsUrl(an.icsdownload).EventInstance— one occurrence in time:id,beginsAt,finishesAt(ISO timestamps). An event has one or more.event.instanceis the next or current one — what most pages show — andevent.instancesis all of them, for a series view or a date picker. OnlygetEventreturnsinstances(andinstance.endDate/endTime);listEventssummaries carry justinstance.Attendance— a contact's RSVP, attached to an instance, never to the event: itsattendableIdis anEventInstance.id. An event with several dates has several instances, each attended independently. An attendance comes back withid,checkedInandcontact.id.
Shapes and gotchas:
- Passing the event id as
attendableIdis the classic mistake — always take it fromevent.instance.idor one ofevent.instances[].id. beginsAt/finishesAtare typed loosely (ISO8601 scalars come through asany); parse them withnew Date(...)before comparing or formatting.listEventshas no date filter: it returns the workspace's events (first/afterpage through them, see Pagination). Filter and sort oninstance.beginsAtyourself.createAttendanceandcreateEventreturn the payload as-is —{ errors, attendance }and{ errors, event }, or null; validation failures come back inerrors, not as a throw. Both need theapiKeyclient, so they run on your server or from the CLI (uidu attendances create,uidu events create), never with the public token in the browser. See Writes and Authentication.
Use cases
Upcoming events page
Goal: a page listing the events that haven't started yet, soonest first.
// app/events/page.tsx — a Server Component
import Link from 'next/link';
import { listEvents } from '@uidu/client';
import { uidu } from '@/lib/uidu';
export default async function EventsPage() {
const events = await listEvents(uidu, { first: 50 });
const now = Date.now();
const upcoming = events
.filter(
(e) =>
e.instance?.beginsAt && new Date(e.instance.beginsAt).getTime() > now,
)
.sort(
(a, b) =>
new Date(a.instance!.beginsAt).getTime() -
new Date(b.instance!.beginsAt).getTime(),
);
return (
<ul>
{upcoming.map((event) => (
<li key={event.id}>
<Link href={`/event/${encodeURIComponent(event.id)}`}>
{event.name}
</Link>
<time>{new Date(event.instance!.beginsAt).toLocaleDateString()}</time>
{event.primaryAddress?.city && (
<span> · {event.primaryAddress.city}</span>
)}
</li>
))}
</ul>
);
}- Each summary has
id,name,description,cover,published,publicPath,isPaidEvent,primaryAddress(formattedAddress,city) andinstance(id,beginsAt,finishesAt). An event with no upcoming occurrence has noinstanceto show. - Put the detail route under a path segment (
/event/[id]), not a root/[id]that would also catch/favicon.ico. - Need every event, for a sitemap or
generateStaticParams? UsepaginatewithListEventsDocument— Pagination & field values.
Event page with RSVP
Goal: show one event with its dates and calendar links, and let a visitor RSVP for a date.
// app/event/[id]/page.tsx
import { createAttendance, getEvent } from '@uidu/client';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu'; // createClient({ workspace, publicToken, apiKey })
export default async function EventPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const event = await getEvent(uidu, { id: decodeURIComponent(id) });
if (!event) notFound();
async function rsvp(formData: FormData) {
'use server';
const result = await createAttendance(uidu, {
input: {
attributes: {
attendableId: String(formData.get('instanceId')), // an INSTANCE id, not event.id
contactAttributes: {
email: String(formData.get('email')),
contactableAttributes: {
kind: 'person',
firstName: String(formData.get('firstName')),
},
},
},
},
});
if (result?.errors?.length) {
// validation failure: return result.errors to the form
return;
}
// result?.attendance?.id, result?.attendance?.contact?.id
}
return (
<article>
<h1>{event.name}</h1>
<p>{event.primaryAddress?.formattedAddress}</p>
<a href={event.toGoogleCalendarUrl ?? undefined}>
Add to Google Calendar
</a>
<a href={event.toIcsUrl ?? undefined}>Download .ics</a>
<form action={rsvp}>
<select name="instanceId" defaultValue={event.instance?.id}>
{(event.instances ?? []).map((i) => (
<option key={i.id} value={i.id}>
{new Date(i.beginsAt).toLocaleString()}
</option>
))}
</select>
<input name="firstName" required />
<input name="email" type="email" required />
<button>RSVP</button>
</form>
</article>
);
}- For a single-date event, skip the picker and send
event.instance.id. AttendanceAttributesbeyond the instance:contactAttributescreates the attendee in the same mutation, orcontactIdattaches an existing contact instead;formResponseAttributescarries extra answers (dietary needs…) in the shape described in Forms — everycontentis{ value };rsvpStatusispending,accepted,declinedortentative.isPaidEvent,hasSeatsandcurrentCapacitytell you whether a plain RSVP fits the event; paid tickets go through uidu's checkout, which this domain doesn't wrap.- The demo's version, with error handling, is
submitRsvpinapps/demo/src/lib/actions.ts.
Publish an event from your server
Goal: create an event with its first date from a script or an admin tool.
import { createEvent, updateEvent } from '@uidu/client';
import { uidu } from '@/lib/uidu'; // must carry apiKey
const created = await createEvent(uidu, {
input: {
attributes: {
name: 'Launch party',
eventInstancesAttributes: [
{
startDate: '2026-11-20',
startTime: '19:00',
endDate: '2026-11-20',
endTime: '23:00',
},
],
},
},
});
if (created?.errors?.length)
throw new Error(created.errors.map((e) => e.name).join(', '));
const id = created?.event?.id;
if (id)
await updateEvent(uidu, { input: { id, attributes: { hasSeats: true } } });- Dates are written per instance as
startDate/endDate(YYYY-MM-DD) andstartTime/endTime(HH:MM); an instance withoutendTimelasts one hour. Reads give you the combinedbeginsAt/finishesAtinstead. EventAttributesalso takesbody(rich text),cover,addressesAttributes,eventTagList,preferences— the full list is in Schema. The payload returns the new event'sidandname.- The same from a terminal:
uidu events create,uidu events update— CLI. Events can't be deleted through the SDK or the CLI.
API at a glance
Every @uidu/client function in Events, generated from the source — the full reference has the rest of the SDK.
| Function | Kind | Auth | CLI | Description |
|---|---|---|---|---|
createAttendance | write | Bearer | attendances create | RSVP a contact to an event: attendableId is an event instance id, never the event id. |
createEvent | write | Bearer | events create | Create an event; returns the payload as-is, { errors, event }, validation failures included. |
getEvent | read | public token | events get | One event with its primary address, its next or current instance and all its instances, or null. |
listEvents | read | public token | events list | The workspace's events as a flat array of summaries, each with its next or current instance; page with first/after. |
updateEvent | write | Bearer | events update | Update an event: { input: { id, attributes } } (create it with createEvent). Returns the payload (with errors) or null. |
Types: Event
CLI:
| Entity | list | get | create | update | delete |
|---|---|---|---|---|---|
events | ✓ | ✓ | ✓ | ✓ | — |
attendances | — | — | ✓ | — | — |