Registrations
Registrations connect participants to events and super events.
To register participants, use Onboard a participant (
POST /super-events/:slug/participants).POST /events/:eventId/registeris deprecated: it can only register the key’s own user, who is an organizer and is refused.
Read
List registrationsGET/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", "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 (a legacy token that check-in scans no longer accept) is NOT returned here. Note
the alternative pagination shape (page/totalPages).
List super event registrationsGET/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", } ], "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 registrationGET/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", "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 (a legacy per-registration token; check-in scans use the participant’s QR token instead, see Check-in), answers_json, the joined user
profile, and joined event metadata.
Write
Register (key's own user) - deprecatedPOST/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).
Deprecated. Use Onboard a participant (
POST /super-events/:slug/participants) instead.
This endpoint always registers the BuilderBase user the key was issued for; a
user_id in the body is ignored. That user is an organizer of the event, and
organizers can’t register for their own events, so the request is refused, normally
with 403 ORGANIZER_CANNOT_REGISTER (other checks, such as closed registration or
missing answers, can answer first). It stays available only for backward
compatibility.
Update registration answersPATCH/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 statusPATCH/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 participantPOST/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": "[email protected]" } |
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": "[email protected]" } |
A pending invite already exists. Idempotent. |
| 200 | { "status": "already_enrolled", "userId": "b3d8e1f0-2c5a-4e9b-8d17-3f6a0c2b5e94" } |
User already has an accepted registration. Idempotent. |
{ "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 | 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 trackPATCH/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 participant’s
personal QR token: their QR code holds the URL
https://builderbase.com/profile/<token>, and you send the <token> part. It is the
same for every event the participant attends. This is the endpoint a kiosk hits
after scanning.
The registration’s
checkin_token(returned by Get registration) is not accepted here and returns404 INVALID_TOKEN.
Scan a check-inPOST/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": "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 personal QR token (the last part of the URL in 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"}'