> ## 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.

# Boot the widget

> The widget's first call, from the developer's own page. Two shapes share it.

**Anonymous** — a publishable key and the device's `anonymous_id` go in; a visitor contact and an unverified session come back. First touch (UTMs, referrer, landing page) is captured here, on the anonymous contact, which is where attribution is born.

**Federated** — `external_id` alongside, optionally `email` and the HMAC `hash`. The hash proves the `external_id` and *nothing else*: an email arriving by boot is stored as an attribute and never resolves to or merges with an existing contact, because anyone can type an email and only a magic link proves one.

A valid hash makes the session verified. An absent hash leaves it unverified — usable in development, marked in the panel, barred from anything another person's data could leak through. A **wrong** hash is refused outright, never downgraded, so a typo in the developer's HMAC code cannot be invisible.

Rate limited to 240 requests per minute per IP, and 240 per minute per publishable key.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/boot
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/boot:
    post:
      tags:
        - Customer plane
      summary: Boot the widget
      description: >-
        The widget's first call, from the developer's own page. Two shapes share
        it.


        **Anonymous** — a publishable key and the device's `anonymous_id` go in;
        a visitor contact and an unverified session come back. First touch
        (UTMs, referrer, landing page) is captured here, on the anonymous
        contact, which is where attribution is born.


        **Federated** — `external_id` alongside, optionally `email` and the HMAC
        `hash`. The hash proves the `external_id` and *nothing else*: an email
        arriving by boot is stored as an attribute and never resolves to or
        merges with an existing contact, because anyone can type an email and
        only a magic link proves one.


        A valid hash makes the session verified. An absent hash leaves it
        unverified — usable in development, marked in the panel, barred from
        anything another person's data could leak through. A **wrong** hash is
        refused outright, never downgraded, so a typo in the developer's HMAC
        code cannot be invisible.


        Rate limited to 240 requests per minute per IP, and 240 per minute per
        publishable key.
      operationId: boot
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - publishable_key
              properties:
                publishable_key:
                  type: string
                  description: '`uk_pk_live_…` or `uk_pk_test_…`.'
                anonymous_id:
                  type: string
                  description: Required unless `external_id` is present.
                name:
                  type: string
                attribution:
                  $ref: '#/components/schemas/Attribution'
                external_id:
                  type: string
                  description: Federated mode only.
                email:
                  type: string
                  format: email
                  description: >-
                    Stored as an attribute. Never an identity edge on this
                    route.
                hash:
                  type: string
                  description: >-
                    `hex(HMAC-SHA256(identity_secret, external_id))`. Computed
                    on the developer's server, never in the browser.
            example:
              publishable_key: uk_pk_live_1f2e…
              anonymous_id: anon_2f9c1b
              attribution:
                utm_source: newsletter
                landing_page: https://example.com/pricing
      responses:
        '200':
          description: The contact already existed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BootResult'
        '201':
          description: The contact was created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BootResult'
        '400':
          description: '`invalid_request` — `anonymous_id` or `external_id` is required.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: >-
            `unauthorized` (missing, malformed or revoked publishable key) or
            `invalid_identity_hash`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/OriginNotAllowed'
        '409':
          description: >-
            `environment_not_federated` — switch the environment to federated to
            identify contacts via boot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
        '501':
          $ref: '#/components/responses/FederatedUnavailable'
      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'
    BootResult:
      type: object
      properties:
        contact:
          $ref: '#/components/schemas/Contact'
        token:
          type: string
          description: Contact session token, `uk_ct_…`.
        expires_at:
          type: string
          format: date-time
        environment:
          type: string
          enum:
            - live
            - test
          description: The publishable key's environment.
        verified:
          type: boolean
          description: True only when a valid HMAC hash proved the `external_id`.
    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
    Contact:
      type: object
      description: One of the developer's own users — anonymous or identified.
      properties:
        id:
          type: string
          format: uuid
        name:
          type:
            - string
            - 'null'
        email:
          type:
            - string
            - 'null'
        avatar_url:
          type:
            - string
            - 'null'
        identified:
          type: boolean
          description: Whether this contact is more than a visitor.
        email_verified:
          type: boolean
          description: >-
            Whether the ADDRESS was proven — by a verification link, a reset
            link or a magic link. Distinct from a session's `verified` flag.
        attribution:
          $ref: '#/components/schemas/Attribution'
        first_seen_at:
          type: string
          format: date-time
        last_seen_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
  responses:
    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'
    FederatedUnavailable:
      description: >-
        `federated_identity_unavailable` — federated identity is unavailable on
        the server.
      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.

````