Registrations
Registrations connect participants to events and super events.
To onboard third parties at scale (people who may not yet have a BuilderBase account), use the Onboarding section below -
POST /events/:id/registercan only register the key’s own user.
Read
List registrations GET /events/:eventId/registrations
{ "data": { "registrations": [ { "id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "event_id": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "user_id": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "status": "accepted", "name": "Alice Rivera", "email": "alice.rivera@northwind.io", "evaluation_score": 8.5, "evaluation_recommendation": "accept" } ], "pagination": { "page": 1, "limit": 50, "total": 142, "totalPages": 3 } }}Query params (all optional):
| Param | Default | Notes |
|---|---|---|
page | 1 | 1-indexed |
limit | 50 | max 100 |
status | - | pending | accepted | rejected | waitlisted | cancelled |
sort_by | created_at | created_at | name | score |
sort_order | desc | asc | desc |
search | - | substring match on participant name and email |
checkin_token is NOT returned here - use the Get registration endpoint. Note
the alternative pagination shape (page/totalPages).
List super event registrations GET /super-events/:superEventId/registrations
{ "data": { "registrations": [ { "id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "event_id": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "user_id": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "status": "accepted", "name": "Alice Rivera", "email": "alice.rivera@northwind.io" } ], "pagination": { "page": 1, "limit": 50, "total": 142, "totalPages": 3 } }}Aggregated across all tracks. Same shape and page/totalPages pagination as the
per-track list.
Get registration GET /registrations/:id
{ "data": { "registration": { "id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "event_id": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "user_id": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "status": "accepted", "checkin_token": "ci_7Kd92mAx4Qp", "answers_json": { "github": "https://github.com/arivera", "track": "autonomous-agents" }, "users": { "id": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "name": "Alice Rivera", "email": "alice.rivera@northwind.io", "linkedin_url": "https://linkedin.com/in/alicerivera", "github_url": "https://github.com/arivera" }, "events": { "id": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "name": "Autonomous Agents Track", "slug": "autonomous-agents" } } }}Includes checkin_token (used by kiosk scanners), answers_json, the joined user
profile, and joined event metadata.
Write
Register (key's own user) POST /events/:eventId/register
{ "data": { "id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "event_id": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "user_id": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "status": "pending", "checkin_token": "ci_7Kd92mAx4Qp" }}{ "answers_json": { "github": "https://github.com/arivera", "track": "autonomous-agents" }}| Field | Type | Required | Notes |
|---|---|---|---|
answers_json | object | No | Custom registration-form responses. A user_id in the body is ignored - the registration is always for the key’s own user. |
super_event_registration_id | string (UUID) | No | The parent super-event application; required when the track belongs to a super event. |
Accepts application/json, or multipart/form-data with file fields named
file_<fieldId> (each uploaded and merged into answers_json).
Path is /register (singular). The body is parsed for answers_json, but the
user_id is always the BuilderBase user the key was issued for - you cannot
register an arbitrary user_id here. To onboard third parties at scale, use the
Onboarding endpoints below. Returns 409 if the user is
already registered.
Update registration answers PATCH /registrations/:id
{ "data": { "id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "answers_json": { "github": "https://github.com/arivera", "track": "autonomous-agents" } }}{ "answers_json": { "github": "https://github.com/arivera", "track": "autonomous-agents" }}| Field | Type | Required | Notes |
|---|---|---|---|
answers_json | object | Yes | The custom registration-form responses - the only mutable field here. |
Accepts application/json, or multipart/form-data with file fields named
file_<fieldId>.
Updates only the participant’s answers_json. For status changes use the
status endpoint below. Accepts application/json (with an answers_json object)
or multipart/form-data if the form contains file uploads. Caller must be the
registration owner (with pending status only) or an event organizer (any status).
Update registration status PATCH /registrations/:id/status
{ "data": { "updated": true, "status": "accepted" }}{ "status": "accepted"}| Field | Type | Required | Notes |
|---|---|---|---|
status | string (enum) | Yes | One of pending, accepted, waitlisted, rejected. |
Allowed statuses: pending, accepted, waitlisted, rejected. Organizer-only.
Setting accepted on a registration tied to an unpaid super event returns
402 EVENT_NOT_PAID - pay via Stripe Checkout in a browser first.
Onboarding (at scale)
For getting people into your hackathon programmatically - whether they already have an account or not. Use this when your registration system lives outside BuilderBase (e.g. Tally, a custom form, an internal CRM) and you push selected applicants in. New emails get a magic link; existing onboarded users are enrolled immediately.
Onboard a participant POST /super-events/:slug/participants
The behaviour depends on whether the email already belongs to a BuilderBase account:
| HTTP | Body | When |
|---|---|---|
| 201 | { "status": "enrolled", "userId": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94" } | Email already belongs to an onboarded user - enrolled immediately with status='accepted'. |
| 201 | { "status": "invited", "email": "alice.rivera@northwind.io" } | Email is new or onboarding unfinished - a magic link is sent; after they click it the account is created and auto-enrolled. |
| 200 | { "status": "already_invited", "email": "alice.rivera@northwind.io" } | A pending invite already exists. Idempotent. |
| 200 | { "status": "already_enrolled", "userId": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94" } | User already has an accepted registration. Idempotent. |
{ "email": "alice.rivera@northwind.io", "firstName": "Alice", "lastName": "Rivera", "linkedinUrl": "https://linkedin.com/in/alicerivera", "githubUrl": "https://github.com/arivera"}| Field | Type | Required | Notes |
|---|---|---|---|
email | string | Yes | Validated and lowercased; invalid format returns 400 INVALID_EMAIL. |
firstName | string | No | Stored on the invite, applied once they accept. |
lastName | string | No | |
linkedinUrl | string (URL) | No | |
githubUrl | string (URL) | No |
Gate failures are surfaced rather than silently bypassed - when you bulk-onboard you know exactly which emails failed and why:
| HTTP | Code | Reason |
|---|---|---|
| 400 | INVALID_EMAIL | Email missing or malformed. |
| 402 | EVENT_NOT_PAID | The hackathon’s payment has not been completed. |
| 403 | PERMISSION_DENIED | Caller is not an org admin nor an organizer on any track. |
| 404 | EVENT_NOT_FOUND | No hackathon found with that slug. |
| 409 | REGISTRATION_CLOSED | Registration is explicitly closed. |
| 409 | MAX_PARTICIPANTS_REACHED | Cap hit; body includes acceptedCount + maxParticipants. |
| 409 | NO_TRACKS | The hackathon has no tracks yet (the invite carries a track FK). |
Assign / move builder to track PATCH /super-events/:superEventId/team-management/builders/:userId/track
Without targetTeamId (track move only):
{ "data": { "result": { "userId": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94", "targetEventId": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16" } }}With targetTeamId (also joins a team):
{ "data": { "result": { "teamId": "7e3a0c92-1b5d-4f86-9c20-2d4a6b8e1f53", "userId": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94" } }}{ "targetTrackId": "5c1f8a73-6d2b-4e90-a1c4-7b8e3f0d2a16", "targetTeamId": "7e3a0c92-1b5d-4f86-9c20-2d4a6b8e1f53", "replacementLeaderUserId": "c4e9f2a1-5d6b-4f0c-9e28-4a7b1d3c6f05"}| Field | Type | Required | Notes |
|---|---|---|---|
targetTrackId | string (UUID) | Yes | Must be a track of the same super event. |
targetTeamId | string | null | No | If provided, the participant joins that team after the move. |
replacementLeaderUserId | string | null | No | Promote a successor if the participant led a team they’re now leaving. |
Assign a participant to a track, or move them between tracks. The move is atomic: the participant must already have an accepted registration on the super event; they get an accepted registration on the target track (with a fresh check-in token), are optionally added to the target team, and are removed from any team they held in another track of the same super event.
Failures: INVALID_TRACK (target track not in this super event),
BUILDER_NOT_FOUND (no accepted super-event registration - use the onboard
endpoint first), TARGET_TRACK_REQUIRED (targetTrackId missing).
Check-in
Records attendance for a registered participant. The token is the registration’s
checkin_token (from Get registration above, or the participant’s QR code) - this
is the endpoint a kiosk hits after scanning.
Scan a check-in POST /checkin-points/:pointId/scan
The endpoint returns one of several shapes depending on the outcome. Handle all of them in a kiosk client:
| Scenario | HTTP | Body |
|---|---|---|
| First successful check-in | 201 | { "data": { "checked_in": true, "checked_in_at": "2026-07-14T09:00:00.000Z", "registration_id": "9a1c3e5f-7b2d-4068-a9e4-1c3b5d7f9028", "point_name": "Main Entrance", "point_type": "attendance" } } |
| Already checked in (attendance) | 200 | { "data": { "already_checked_in": true, "checked_in_at": "2026-07-14T09:00:00.000Z" } } |
| Doorkeeper re-scan | 200 | { "data": { "verified": true, "first_check_in_at": "2026-07-14T09:00:00.000Z" } } |
| Milestone gate | 200 | { "data": { "requires_milestone_submission": true, "milestone_requirements": [...], "team_id": "7e3a0c92-1b5d-4f86-9c20-2d4a6b8e1f53", "team_name": "Beat Wizards" } } |
| Invalid token | 404 | { "code": "INVALID_TOKEN" } |
| Registration not accepted | 403 | { "code": "NOT_ACCEPTED" } |
| Window not yet open | 400 | { "code": "TOO_EARLY" } |
| Window closed | 400 | { "code": "TOO_LATE" } |
| Check-in point not found | 404 | { "code": "POINT_NOT_FOUND" } |
| Manual method, non-organizer | 403 | { "code": "ORGANIZER_REQUIRED" } |
| Milestone point, no team | 400 | { "code": "NO_TEAM" } |
On the milestone gate the check-in is not recorded - submit the milestone, then re-scan.
{ "token": "ci_7Kd92mAx4Qp", "method": "qr_scan"}| Field | Type | Required | Notes |
|---|---|---|---|
token | string | Yes | The participant’s check-in token (e.g. from their QR code). |
method | string (enum) | No | qr_scan (default), self_check, or manual. |
If method is manual, the key creator must be an organizer of the event -
otherwise 403 ORGANIZER_REQUIRED.
The endpoint your kiosk hits after reading a participant’s QR code.
curl -X POST https://api.builderbase.com/checkin-points/<pointId>/scan \ -H "X-Api-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"token": "<participant_token>", "method": "qr_scan"}'