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

# Read what the plan allows

> What this organization's PLAN allows — a different question from what the caller's ROLE allows. Permission answers "is this person allowed", entitlement answers "is this plan allowed", and neither implies the other: an owner holds every permission there is and is still not entitled to a fourth staff seat on the free plan.

Any member may read it, behind no permission of its own — it is what a locked screen explains itself with, and hiding the reason a button is missing is worse than showing it.

Every feature in the catalogue is present, including the ones no plan carries, so a client can render an offer rather than an absence. `limit` is `null` whenever there is no number to send, and `unlimited` is what says which null it is: an allowance with no ceiling (`true`) against a ceiling of zero (`false`) — a feature this plan does not carry, or one that is not measured by a number at all. Do not read a bare `null` as an unlimited allowance; it is the same value on a feature nobody is entitled to.

**No authentication method ever appears here.** Password, magic link, one-time code, social and two-factor are free forever and are not in the catalogue at all. Resold SSO (SAML, SCIM) is the exception that makes the rule precise: it is a capability a tenant sells onward to their own enterprise customer, priced per connection, not a way somebody signs in.

This is a read gate and it fails **open**: when entitlements cannot be resolved the plan comes back as `unknown` with everything enabled, because an entitlements outage must not become a product outage. The API's own gates remain the enforcement either way.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/organization/entitlements
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/organization/entitlements:
    get:
      tags:
        - Organizations
      summary: Read what the plan allows
      description: >-
        What this organization's PLAN allows — a different question from what
        the caller's ROLE allows. Permission answers "is this person allowed",
        entitlement answers "is this plan allowed", and neither implies the
        other: an owner holds every permission there is and is still not
        entitled to a fourth staff seat on the free plan.


        Any member may read it, behind no permission of its own — it is what a
        locked screen explains itself with, and hiding the reason a button is
        missing is worse than showing it.


        Every feature in the catalogue is present, including the ones no plan
        carries, so a client can render an offer rather than an absence. `limit`
        is `null` whenever there is no number to send, and `unlimited` is what
        says which null it is: an allowance with no ceiling (`true`) against a
        ceiling of zero (`false`) — a feature this plan does not carry, or one
        that is not measured by a number at all. Do not read a bare `null` as an
        unlimited allowance; it is the same value on a feature nobody is
        entitled to.


        **No authentication method ever appears here.** Password, magic link,
        one-time code, social and two-factor are free forever and are not in the
        catalogue at all. Resold SSO (SAML, SCIM) is the exception that makes
        the rule precise: it is a capability a tenant sells onward to their own
        enterprise customer, priced per connection, not a way somebody signs in.


        This is a read gate and it fails **open**: when entitlements cannot be
        resolved the plan comes back as `unknown` with everything enabled,
        because an entitlements outage must not become a product outage. The
        API's own gates remain the enforcement either way.
      operationId: organizationEntitlements
      parameters:
        - $ref: '#/components/parameters/OrganizationHeader'
      responses:
        '200':
          description: The plan and its entitlements.
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan:
                    type: string
                    enum:
                      - free
                      - pro
                      - unknown
                    description: >-
                      `unknown` means entitlements could not be resolved, and
                      everything is enabled.
                  resolved:
                    type: boolean
                    description: >-
                      `false` when this is the fail-open default rather than a
                      plan's answer — the same case `plan: unknown` names, said
                      in a field a client can branch on. The body is still
                      complete and still truthful: UserKit's own catalogue is
                      compiled in, so every feature is here with every ceiling
                      gone. What the field buys is a screen not presenting an
                      outage as an upgrade.
                  features:
                    type: object
                    description: Keyed by feature key.
                    additionalProperties:
                      type: object
                      properties:
                        kind:
                          type: string
                          enum:
                            - boolean
                            - limit
                            - per_unit
                        enabled:
                          type: boolean
                        limit:
                          type:
                            - integer
                            - 'null'
                          format: int64
                          description: >-
                            The ceiling. `null` means there is no NUMBER to
                            send, which is three different answers — read
                            `unlimited` to tell them apart: `null` with
                            `"unlimited": true` is an allowance with no ceiling,
                            and `null` with `"unlimited": false` is a ceiling of
                            zero (a feature this plan does not carry, or a kind
                            that is not measured against one).
                        unlimited:
                          type: boolean
                          description: >-
                            Whether the allowance really has no ceiling.
                            Declared rather than inferred from `limit: null`:
                            the two nulls are opposite answers, and the
                            reassuring reading of the ambiguous one grants a
                            feature nobody is entitled to.
              example:
                plan: free
                resolved: true
                features:
                  staff_seats:
                    kind: limit
                    enabled: true
                    limit: 3
                    unlimited: false
                  analytics_retention:
                    kind: limit
                    enabled: true
                    limit: 30
                    unlimited: false
                  audit_log:
                    kind: limit
                    enabled: true
                    limit: 30
                    unlimited: false
                  billing_module:
                    kind: boolean
                    enabled: false
                    limit: null
                    unlimited: false
                  saml_connections:
                    kind: per_unit
                    enabled: false
                    limit: null
                    unlimited: false
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  parameters:
    OrganizationHeader:
      name: X-Organization-Id
      in: header
      required: false
      schema:
        type: string
      description: >-
        The organization the caller is acting on — the `org_…` code that appears
        in the panel URL. It *identifies*; the membership JOIN is what
        *authorizes*, so a forged code reads nothing. Absent, the session's
        default organization answers.
  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'
  schemas:
    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
  securitySchemes:
    sessionToken:
      type: http
      scheme: bearer
      description: >-
        A staff session token, `uk_st_…`. Minted by sign-up, sign-in or the
        two-factor exchange.

````