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

# Join the waitlist

> Puts somebody in line before the product opens. Unconditional `202`, like every public endpoint that takes an address, and it mints no session — the developer lets people in from their panel and the person hears about it by email.

The entry hangs off a **contact**, so first-touch attribution, the merge and the audit trail apply to it from its first minute. Joining twice keeps the first place in line and the first answers.

Only in an environment that is holding a line: an open one answers `409 waitlist_closed` rather than collecting into nothing.

Rate limited to 10 requests per hour per IP.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/waitlist
openapi: 3.1.0
info:
  title: UserKit API
  version: 1.0.0
  description: >-
    The HTTP surface of UserKit.


    Two planes share one API. The **staff plane** is what a human uses in the
    panel: users, organizations, roles, members, keys. The **customer plane** is
    what a developer's own product uses: contacts, identities, hosted or
    federated sign-in.


    Every error answers the same envelope — `{"error": {"code", "message"}}`.
    The `code` is a stable contract to branch on; the `message` is for a person
    and may change.
servers:
  - url: '{baseUrl}'
    description: The API host.
    variables:
      baseUrl:
        default: https://api.userkit.dev
        description: Base URL of the API, no trailing slash.
security:
  - sessionToken: []
tags:
  - name: Authentication
    description: >-
      Public sign-up, sign-in, two-factor and password recovery for staff
      accounts.
  - name: Session
    description: >-
      The active session: who the caller is, which organization they are in, and
      signing out.
  - name: Account
    description: The caller's own account — profile, password, sessions, avatar.
  - name: Two-factor
    description: >-
      TOTP setup, activation and recovery codes. Returns 501 when two-factor is
      unavailable on the server.
  - name: Organizations
    description: The organizations a user belongs to, and the active one.
  - name: Environments
    description: >-
      The live and test environments seeded with every organization, and their
      identity settings.
  - name: Members
    description: Memberships and invitations.
  - name: Roles
    description: Roles and the permission catalogue they draw from.
  - name: API keys
    description: Secret keys (`uk_sk_…`) and publishable keys (`uk_pk_…`).
  - name: Contacts (panel)
    description: >-
      The staff view of the customer plane. Reads take `?environment=` as an
      explicit view parameter.
  - name: Contacts (server)
    description: >-
      The machine surface, authenticated by an API key. The environment is the
      key's environment and cannot be named by the caller.
  - name: Customer plane
    description: >-
      Called from the developer's own pages with a publishable key: boot, magic
      link, and the contact's own session.
  - name: Hosted auth
    description: >-
      Sign-up, sign-in, verification and recovery for contacts, when UserKit
      owns the account.
  - name: System
    description: Health and the internal job callback.
paths:
  /v1/waitlist:
    post:
      tags:
        - Customer plane
      summary: Join the waitlist
      description: >-
        Puts somebody in line before the product opens. Unconditional `202`,
        like every public endpoint that takes an address, and it mints no
        session — the developer lets people in from their panel and the person
        hears about it by email.


        The entry hangs off a **contact**, so first-touch attribution, the merge
        and the audit trail apply to it from its first minute. Joining twice
        keeps the first place in line and the first answers.


        Only in an environment that is holding a line: an open one answers `409
        waitlist_closed` rather than collecting into nothing.


        Rate limited to 10 requests per hour per IP.
      operationId: joinWaitlist
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - publishable_key
                - email
              properties:
                publishable_key:
                  type: string
                email:
                  type: string
                  format: email
                name:
                  type: string
                anonymous_id:
                  type: string
                  description: >-
                    The visitor this person already was, so the list inherits
                    where they came from.
                attribution:
                  $ref: '#/components/schemas/Attribution'
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Whatever your form asked beyond the address. Capped at 4
                    KiB.
      responses:
        '202':
          description: In line.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/PublishableKeyInvalid'
        '403':
          $ref: '#/components/responses/OriginNotAllowed'
        '409':
          description: >-
            `waitlist_closed` — this environment is not holding a line.
            `environment_not_hosted` — it is federated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
      security: []
components:
  schemas:
    Attribution:
      type: object
      description: >-
        First touch, captured on the visitor at first load and preserved through
        a merge. All fields optional; empty means unknown.
      properties:
        utm_source:
          type:
            - string
            - 'null'
        utm_medium:
          type:
            - string
            - 'null'
        utm_campaign:
          type:
            - string
            - 'null'
        utm_term:
          type:
            - string
            - 'null'
        utm_content:
          type:
            - string
            - 'null'
        referrer:
          type:
            - string
            - 'null'
        landing_page:
          type:
            - string
            - 'null'
    Error:
      type: object
      description: Every error in this API answers this envelope.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Stable. Branch on this.
            message:
              type: string
              description: For a person. May change.
          required:
            - code
            - message
      required:
        - error
      example:
        error:
          code: forbidden
          message: your role does not allow this action
  responses:
    BadRequest:
      description: '`invalid_request` — the body or a parameter is malformed.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    PublishableKeyInvalid:
      description: '`unauthorized` — missing, malformed or revoked publishable key.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    OriginNotAllowed:
      description: >-
        `origin_not_allowed` — this publishable key does not accept calls from
        this origin.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: >-
        `rate_limited` — too many requests. `Retry-After` carries the window in
        seconds.


        The counters are shared across instances. When that store cannot be
        reached each instance counts on its own instead: the limits get looser,
        never absent.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the window resets.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    sessionToken:
      type: http
      scheme: bearer
      description: >-
        A staff session token, `uk_st_…`. Minted by sign-up, sign-in or the
        two-factor exchange.

````