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

# List contacts

> Scoped to the key's environment. There is no environment parameter on this surface, by design.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/contacts
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: Audit log
    description: >-
      What staff did inside an organization. Append-only, and read behind its
      own permission.
  - name: API keys
    description: Secret keys (`uk_sk_…`) and publishable keys (`uk_pk_…`).
  - name: Contacts (staff session)
    description: >-
      The staff view of the customer plane, opened by a staff session. Reads
      take `?environment=` as an explicit view parameter.
  - name: Contacts (API key)
    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: Customer teams
    description: >-
      A customer is a team. Its roster, its invitations and its two roles —
      owner and member — administered by the contact's own session. The active
      customer travels in `X-Customer-Id`.
  - name: Webhooks
    description: >-
      Outbound webhooks: endpoints, the published event catalogue, the delivery
      log, replay and test sends. Never gated by a plan — webhooks are a
      developer primitive.
  - name: Catalogue
    description: >-
      The plans, prices and features **you** sell to your own customers. Per
      environment, a price per currency, and `recurring` or `one_time`. Distinct
      from the plan you are on with UserKit, which is `GET
      /v1/organization/entitlements`.
  - name: Subscriptions
    description: >-
      What one of **your** customers is paying you, mirrored from the gateway
      that charges them. The gateway is the truth about money — it holds the
      schedule, runs the retries and decides what a proration is worth — so
      these routes ask it to change something and answer with what it then said.
      `provider_synced_at` is how stale the copy admits to being.
  - name: Provider webhooks
    description: >-
      Where a payment provider delivers to. Not a surface you call — it is a URL
      you paste into the provider's dashboard, which is why it sits outside
      `/v1`: a version bump must never mean editing a setting in somebody else's
      product. Signed with the secret of the connection named in the path, and
      safe to retry.
  - name: Entitlements
    description: >-
      What one of **your** customers may do, resolved: the plan their
      subscription carries, plus the overrides you promised them on top. This is
      the read your own gate calls, so it is cached and answers in one round
      trip. It is a different question from `GET /v1/organization/entitlements`,
      which is the plan **you** are on with UserKit — two catalogues, same word,
      different money.
paths:
  /v1/contacts:
    get:
      tags:
        - Contacts (API key)
      summary: List contacts
      description: >-
        Scoped to the key's environment. There is no environment parameter on
        this surface, by design.
      operationId: listContacts
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: The contacts.
          headers:
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
            X-RateLimit-Scope:
              $ref: '#/components/headers/RateLimitScope'
          content:
            application/json:
              schema:
                type: object
                properties:
                  contacts:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/ApiKeyRateLimited'
      security:
        - apiKey: []
components:
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        default: 50
        minimum: 1
        maximum: 200
      description: Page size. Values outside 1–200 fall back to 50.
    Offset:
      name: offset
      in: query
      required: false
      schema:
        type: integer
        default: 0
        minimum: 0
  headers:
    RateLimitLimit:
      schema:
        type: integer
      description: >-
        Requests allowed in the current window, for whichever ceiling is closest
        to running out — `X-RateLimit-Scope` says which one that is.
    RateLimitRemaining:
      schema:
        type: integer
      description: >-
        Requests left in the current window under that same ceiling. Zero means
        the next request is refused.
    RateLimitReset:
      schema:
        type: integer
      description: >-
        Seconds until the window resets — a *delta*, which is what this header
        name means in the IETF draft. On a `429` it is the same number as
        `Retry-After`.
    XRateLimitLimit:
      schema:
        type: integer
      description: >-
        The same number as `RateLimit-Limit`, under the name most existing
        clients already read.
    XRateLimitRemaining:
      schema:
        type: integer
      description: The same number as `RateLimit-Remaining`.
    XRateLimitReset:
      schema:
        type: integer
      description: >-
        When the window resets, as a Unix epoch second — the *absolute* form
        that goes with this header name. It marks the same instant
        `RateLimit-Reset` expresses as a delta; the two units are deliberate,
        not a mistake.
    RateLimitScope:
      schema:
        type: string
        enum:
          - key
          - environment
      description: >-
        Which ceiling the three numbers describe: `key` is this API key's own
        allowance, `environment` the one it shares with every other key of the
        same environment. Informational — branch on the numbers, not on this.
  schemas:
    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.
        two_factor_enabled:
          type: boolean
          description: >-
            Whether this contact demands a second factor. Every path that mints
            a session honours it, not only the password one — so an account
            screen reads this to decide between offering enrolment and offering
            to turn it off. `DELETE /v1/organization/contacts/{id}/two-factor`
            is the only way it comes off that is not the contact's own act.
        password_breached:
          type: boolean
          description: >-
            The password this contact signs in with was found in a public breach
            corpus. Sign-in is never refused because of it — the check runs
            after the fact, and the contact is emailed once, in your
            organization's name. Setting a new password clears it.
        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
    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:
    Unauthorized:
      description: >-
        `unauthorized` — missing, malformed or expired credential. A well-formed
        credential from the wrong family says so: "this endpoint expects a staff
        session token, not an organization API key".
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ApiKeyRateLimited:
      description: >-
        `rate_limited` — this API key has spent its allowance for the current
        minute, or the environment's keys have spent theirs between them.
        `Retry-After` says how long is left of the window, and the `RateLimit-*`
        headers say which of the two ceilings refused.


        Nothing durable happened, so the retry is free: a throttled request
        never claims an `Idempotency-Key`, and the same key is still spendable
        on the next attempt.


        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, and `RateLimit-Remaining` is then that instance's
        remainder rather than the whole fleet's.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the window resets.
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
        X-RateLimit-Scope:
          $ref: '#/components/headers/RateLimitScope'
      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.
    apiKey:
      type: http
      scheme: bearer
      description: >-
        An organization API key, `uk_sk_live_…` or `uk_sk_test_…`. The
        environment is resolved from the stored key row on every request, never
        from a request parameter.


        Every route behind this credential is rate limited **per key** — 1000
        requests a minute — with a second ceiling of 3000 a minute across all
        the keys of one environment, so splitting your traffic across keys
        isolates it and minting more keys does not buy more of it. A test key
        can never spend a live key's allowance. Every response carries the
        current state in headers; see the rate-limits guide.

````