Skip to content

Bulk Enroll Attendees to Event

The bulkEnrollAttendeesToEvent mutation enrolls multiple users into a single event in one request. Use it when importing an external list of users (e.g., a course’s student roster) into an event.

Each row creates a Payment with amount: 0 (comp seat semantics). See the Event Mutations overview.

  • Maximum batch size: 200 rows per call. Larger jobs should be split into multiple calls.
  • Duplicate userId or email values within the same batch are rejected (EVENT-008).
FieldTypeRequiredDescription
eventIdString!YesID of the event
inputs[AdminEventAttendeeBulkInput!]!YesPer-attendee rows (max 200)
ticketIdStringNoTicket applied to all rows. Defaults to event.tickets.first.
atomicBooleanNoWhen true, any row failure rolls back the entire batch. Default: false (independent rows).
FieldTypeRequiredDescription
userIdStringNo*Existing student ID
emailStringNo*Student email; creates a placeholder user if no match
nameStringNoRequired when creating a user via email. Ignored when email matches an existing student; the stored name is preserved.

*Each row must provide either userId or email.

type AdminBulkEnrollAttendeesToEventPayload {
results: [AdminEventAttendeeBulkResult!]
allSucceeded: Boolean
}
type AdminEventAttendeeBulkResult {
attendee: AdminEventAttendee # null when the row failed
wasNewlyCreated: Boolean # false on idempotent re-enroll
errors: [AdminBulkRowError!] # null when the row succeeded
}
type AdminBulkRowError {
code: String! # Stable error code; switch on this in client code.
message: String! # Human-readable message (locale follows the school default).
}

Switching on .code lets you classify per-row outcomes deterministically:

for (const [i, row] of results.entries()) {
if (row.attendee) continue;
const { code } = row.errors![0];
if (code === "EVENT-003") retryQueue.push(inputs[i]); // transient
else if (code === "EVENT-011") atomicRollbackCount++; // atomic mode rollback
else if (code.startsWith("ENROLLMENT-")) skip(inputs[i], code); // input issue, skip row
else alert("unexpected", code);
}

results[i] always corresponds to inputs[i] — order is preserved.

AdminEventAttendee in each row exposes id (Enrollment id), userId, paymentId, email, name, attendStatus, checkInAt, and createdAt. See Enroll Attendee → AdminEventAttendee Object for full field details. Pass attendee.id (or attendee.userId / attendee.email) to removeAttendeeFromEvent if you need to revert a row.

Each row is processed independently. A failure on one row does not affect other rows. Per-row errors appear in results[i].errors.

EVENT-008 only rejects duplicates within the same batch (two rows in inputs pointing at the same user). Rows that point at users already enrolled in the event are accepted and go through the idempotent path — wasNewlyCreated: false, no new Payment is created.

All rows are wrapped in a single transaction. The first row failure triggers a rollback of every row in the batch. After rollback:

  • The failing row’s results[i].errors[0] carries the original {code, message}.
  • Every other row’s results[i].errors[0] carries {code: "EVENT-011", message: "Rolled back due to row N failure"}. Filter on this code to count rollback losses for monitoring.
  • No enrollments or payments are persisted.

Atomic mode caveat: side effects from deliver_later mailers or already-enqueued background jobs cannot be unwound on rollback. Use atomic mode for “all or nothing” data integrity, not for guaranteeing no notifications fire.

mutation BulkEnroll {
bulkEnrollAttendeesToEvent(
eventId: "evt_01HQ..."
atomic: false
inputs: [
{ userId: "usr_01ABC..." }
{ email: "newuser@example.com", name: "New User" }
]
) {
results {
attendee { id userId email }
wasNewlyCreated
errors { code message }
}
allSucceeded
}
}
CodeDescription
EVENT-001Event not found
EVENT-002Ticket not found or does not belong to this event
EVENT-004Event is canceled or completed
EVENT-005Event has no ticket configured
EVENT-007Batch size invalid (inputs empty, or > 200 rows)
EVENT-008Duplicate userId or email within the batch

Per-row errors (in results[i].errors) draw from the same set as enrollAttendeeToEvent, plus:

CodeWhere it appearsDescription
EVENT-011atomic mode rollback rowsRow was rolled back because an earlier row failed; the failing row’s own code is unaffected.
GENERAL-001per-row rescueUnexpected error during this row. Treat as transient and surface to alerting; do not silently retry without inspection.

Requires students:write.

For more information about the Teachify Admin API, please refer to the API Overview.