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

# Fetch an environment's public keys

> The public half of the verification path: the JWKS document your backend checks session JWTs against.

Addressed by **publishable key** — the one identifier your backend already holds in config — rather than by an internal id you would have to look up. Public and unauthenticated by design: these are public keys. There is no Origin gate either, because the caller is a server and servers send no Origin.

The response carries `Cache-Control: public, max-age=300, stale-while-revalidate=86400`. That is deliberate: a verifier must keep verifying through our bad five minutes, so a CDN or an in-process cache should keep answering while a refetch is retried.

Keys are minted on first read, so the document is never empty for a configured environment. When JWT minting is unavailable on the server, the answer is an **empty key set** rather than an error — the JWKS shape stays valid.

Rate limited to 120 requests per minute per IP; a backend may poll it.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/jwks/{publishableKey}
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/jwks/{publishableKey}:
    get:
      tags:
        - Customer plane
      summary: Fetch an environment's public keys
      description: >-
        The public half of the verification path: the JWKS document your backend
        checks session JWTs against.


        Addressed by **publishable key** — the one identifier your backend
        already holds in config — rather than by an internal id you would have
        to look up. Public and unauthenticated by design: these are public keys.
        There is no Origin gate either, because the caller is a server and
        servers send no Origin.


        The response carries `Cache-Control: public, max-age=300,
        stale-while-revalidate=86400`. That is deliberate: a verifier must keep
        verifying through our bad five minutes, so a CDN or an in-process cache
        should keep answering while a refetch is retried.


        Keys are minted on first read, so the document is never empty for a
        configured environment. When JWT minting is unavailable on the server,
        the answer is an **empty key set** rather than an error — the JWKS shape
        stays valid.


        Rate limited to 120 requests per minute per IP; a backend may poll it.
      operationId: jwks
      parameters:
        - name: publishableKey
          in: path
          required: true
          schema:
            type: string
          description: '`uk_pk_live_…` or `uk_pk_test_…`.'
      responses:
        '200':
          description: >-
            The environment's servable keys — the active one, plus any rotated
            out within the grace window.
          headers:
            Cache-Control:
              schema:
                type: string
              description: '`public, max-age=300, stale-while-revalidate=86400`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JWKS'
              example:
                keys:
                  - kty: EC
                    crv: P-256
                    x: f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU
                    'y': x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0
                    kid: 6f1c…
                    alg: ES256
                    use: sig
        '404':
          description: '`not_found` — unknown or malformed publishable key.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
      security: []
components:
  schemas:
    JWKS:
      type: object
      description: >-
        A JSON Web Key Set. Standard shape, so any JWT library reads it without
        help.
      properties:
        keys:
          type: array
          items:
            type: object
            properties:
              kty:
                type: string
                const: EC
              crv:
                type: string
                const: P-256
              x:
                type: string
              'y':
                type: string
              kid:
                type: string
                description: >-
                  Matches the `kid` in a token's header — how a verifier picks
                  the right key.
              alg:
                type: string
                const: ES256
              use:
                type: string
                const: sig
    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:
    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.

````