> ## Documentation Index
> Fetch the complete documentation index at: https://attend.hackclub.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Invite a participant

> Send an event invitation email and put the person on the roster.

The registration is created with status `invited` — the same state a CSV import leaves people in. They move to `in_progress` on their own when they first open the onboarding wizard. `first_name`/`last_name` are optional; when omitted the participant is created with placeholder legal names that are replaced from their account on first sign-in.

`date_of_birth`, `phone`, `country` and the address fields are optional too. Everything sent is stored on the participant; the name, birthday, phone and country also prefill the Hack Club Auth signup form when they open the invitation and don't have an account yet. If the email already belongs to someone Attend knows (from another event), only the fields they have no value for are filled in; nothing they entered is overwritten. The address counts as one value: if any part of one is on file, none of the address fields sent are written.

While the event is holding onboarding invitations (`modules.onboarding_invites_held`), the invitation is recorded but not emailed: it goes out when the event's held invitations are released from the dashboard. The response says which happened. A held invitation still creates the `invited` registration, and the wizard stays shut to them until it is released.



## OpenAPI

````yaml /openapi.yml post /events/{event_id}/participants
openapi: 3.0.3
info:
  title: Attend API
  version: v1
  description: >
    REST API for [Attend](https://attend.hackclub.com), Hack Club's event

    management platform. The API is used by the Attend mobile app and by

    trusted server-to-server integrations.


    ## Authentication


    Almost every endpoint requires a bearer token:


    ```

    Authorization: Bearer <token>

    ```


    Four kinds of token are accepted:


    - **Mobile token** — issued to a signed-in user by `POST /session`. Sets the
      current user. May return the response header
      `X-Token-Refresh-Recommended: true` when the token is nearing expiry;
      call `POST /session/refresh` to rotate it.
    - **Global API token** — a superadmin-issued token. Only honoured while its
      owner is still a global admin. Sets the current user. May be **scoped**
      when issued, in which case it reaches only the endpoints that accept
      that scope and returns `403` everywhere else, read-only endpoints
      included. An unscoped token carries its owner's full access. Scopes are
      noted on the endpoints that accept them: `bans:write` and
      `support:read`.
    - **Series API key** — one key for a whole event series, issued from
      **Series → API Keys** in the dashboard by a series owner. Does **not** set
      a current user. It acts as an event API key on *every* event in its
      series, and it is the only credential that can create an event. Scopes do
      not apply — a series key's reach is its series. See the **Series**
      section.
    - **Event API key** — per-event key (`EventApiToken`, or the legacy
      `Event#api_key`). Does **not** set a current user, so it only works on the
      subset of endpoints that don't require one (participant `lookup`,
      `roster`, `create`, `update` and `delete`, event setup, event staff, and
      the travel calendar).

    Missing or invalid credentials return `401 { "error": "Unauthorized" }`.


    ### What an API key cannot do


    Neither key kind sets a current user, so endpoints that must attribute work

    to a person stay closed to both — regardless of how broad the key is. A

    series key is wider in *reach* (more events), never in *depth*:


    | Endpoint | Series key | Event key |

    | --- | --- | --- |

    | `GET/POST /series/…` | ✅ its own series | ❌ `403` |

    | `POST /series/{id}/events` | ✅ its own series | ❌ `403` |

    | Participant `lookup`, `roster`, `create` | ✅ any event in the series | ✅
    its own event |

    | `PATCH`/`DELETE` a participant | ✅ any event in the series | ✅ its own
    event |

    | `/events/{id}/setup/…` and `/events/{id}/staff` | ✅ any event in the
    series | ✅ its own event |

    | `GET /events/{id}/travel` | ✅ any event in the series | ✅ its own event |

    | `GET /events/{id}/participants` (full payload) | ❌ `403` | ❌ `403` |

    | Notes, scans, scan contexts, Slack blasts, NFC badges | ❌ `403` | ❌ `403`
    |

    | `/events/{id}/messages/…` and `/events/{id}/incidents/…` | ❌ `403` | ❌
    `403` |

    | `GET /incident_reports/options` and `POST /incident_reports` | ❌ `403` | ❌
    `403` |

    | `GET /messages` and `GET /messages/{id}` (inbox) | ❌ `403` | ❌ `403` |


    Either key can be revoked without credentials by POSTing its own secret to

    `POST /tokens/revoke`; Attend emails the key's owner (or, if their account

    is gone, the series owners) when that happens.


    ## Conventions


    - All timestamps are ISO 8601 strings and may be `null` where noted.

    - Errors use the envelope `{ "error": "<message>" }`.

    - `:event_id` path segments accept a numeric id or the event slug on the
      participants, setup, and staff endpoints; other endpoints accept the
      numeric id only.
    - A caller that may *write* a field cannot always *read* it back: dates of
      birth, phone numbers, addresses and the details of the people around a
      participant are omitted (not nulled) for API keys and PII-restricted
      roles, in responses to writes just as in responses to reads.

    > **Note:** Inbound webhook receivers (Postmark, Help Scout, DocuSeal,

    > Slack events) also live under `/api/v1` but are called by third parties,

    > not by API consumers, and are not documented here.
servers:
  - url: https://attend.hackclub.com/api/v1
    description: Production
  - url: http://localhost:3000/api/v1
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: Obtain, refresh, and revoke mobile bearer tokens.
  - name: Account
    description: The authenticated user's profile and accessible events.
  - name: Series
    description: >-
      Event series, and the events inside one, addressed with a single series
      API key. Creating an event lives here — an event has to belong to a
      series, and only a series key names one unambiguously.
  - name: Event Setup
    description: >-
      The tail of the setup wizard — waiver configuration and taking an event
      out of draft — for events created through the API.
  - name: Event Staff
    description: Who has access to an event, and what they can do there.
  - name: Participants
    description: Participant registrations for an event.
  - name: Notes
    description: Staff notes on a participant's registration.
  - name: Scans
    description: QR / NFC / manual scans and check-in.
  - name: Scan Contexts
    description: The scan stations configured for an event.
  - name: Stays
    description: >-
      An event's stays — during the event, the night before, the night after —
      and the roster of who's staying for each list-only stay. Built for a shop
      that sells the night: add buyers to the roster on purchase, remove them on
      refund. Accepts an event or series API key, or a global token scoped to
      `rooming:write`.
  - name: Travel Calendar
    description: Chronological participant travel and pickup operations.
  - name: Slack Blasts
    description: Broadcast Slack messages to an event's participants.
  - name: Messages
    description: >-
      Compose, preview, schedule and send an organizer broadcast over Slack,
      email, SMS and push, and follow its deliveries.
  - name: Incidents
    description: >-
      An event's incident log — logging, sharing, commenting on and escalating
      incidents to Hack Club HQ.
  - name: NFC Badges
    description: Provision and manage participant NFC badges.
  - name: Push Tokens
    description: Register device push-notification tokens.
  - name: Inbox
    description: The organizer messages delivered to the signed-in participant.
  - name: Incident Reports
    description: File a confidential incident report straight to Hack Club HQ.
  - name: Bans
    description: The app-wide ban list. Global admins only.
  - name: Travel
    description: >-
      Airport and flight lookups. These endpoints authenticate with the web
      session cookie (Devise), not a bearer token.
paths:
  /events/{event_id}/participants:
    post:
      tags:
        - Participants
      summary: Invite a participant
      description: >-
        Send an event invitation email and put the person on the roster.


        The registration is created with status `invited` — the same state a CSV
        import leaves people in. They move to `in_progress` on their own when
        they first open the onboarding wizard. `first_name`/`last_name` are
        optional; when omitted the participant is created with placeholder legal
        names that are replaced from their account on first sign-in.


        `date_of_birth`, `phone`, `country` and the address fields are optional
        too. Everything sent is stored on the participant; the name, birthday,
        phone and country also prefill the Hack Club Auth signup form when they
        open the invitation and don't have an account yet. If the email already
        belongs to someone Attend knows (from another event), only the fields
        they have no value for are filled in; nothing they entered is
        overwritten. The address counts as one value: if any part of one is on
        file, none of the address fields sent are written.


        While the event is holding onboarding invitations
        (`modules.onboarding_invites_held`), the invitation is recorded but not
        emailed: it goes out when the event's held invitations are released from
        the dashboard. The response says which happened. A held invitation still
        creates the `invited` registration, and the wizard stays shut to them
        until it is released.
      parameters:
        - $ref: '#/components/parameters/EventId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                first_name:
                  type: string
                last_name:
                  type: string
                date_of_birth:
                  type: string
                  format: date
                  example: '2009-04-02'
                  description: Can't be in the future.
                phone:
                  type: string
                  description: E.164 format, e.g. +12025550100
                country:
                  type: string
                  description: >-
                    ISO 3166-1 alpha-2 code (e.g. `US`) or the country's English
                    name.
                address_line_1:
                  type: string
                address_line_2:
                  type: string
                city:
                  type: string
                state:
                  type: string
                postal_code:
                  type: string
      responses:
        '201':
          description: Invitation enqueued, or recorded as held.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  held:
                    type: boolean
                    example: false
                    description: >-
                      True when the event is holding onboarding invitations and
                      this one was recorded without an email.
                  message:
                    type: string
                    example: Invitation sent to jane@example.com
                  event:
                    type: string
                    example: My Event
                  participant_id:
                    type: string
                    format: uuid
                    description: The person created or matched for this invitation.
                  participant_event_id:
                    type: string
                    format: uuid
                    description: Their registration for this event.
                  status:
                    type: string
                    example: invited
                    description: Registration status, `invited` on creation.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Already invited, or already registered for this event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteError'
        '422':
          description: Missing/invalid email, or the email is banned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteError'
components:
  parameters:
    EventId:
      name: event_id
      in: path
      required: true
      schema:
        type: string
      description: Event id or slug.
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: The caller cannot access this event or action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Event or record not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    InviteError:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: This email is already registered for this event
    Error:
      type: object
      properties:
        error:
          type: string
          example: Unauthorized
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Mobile token, global API token, series API key, or event API key. See
        Authentication above for which endpoints each one reaches.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.