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

# Replace a role's permissions

> Requires `roles:manage`. The permission set is **replaced**, not merged — the editor sends the full set, and a merge would make removing a permission impossible.

A system role's permissions can be edited (that is how an organization tailors `admin` to itself) but its name cannot: the seeding code and the last-owner guard match on `owner` by name. The owner role must keep `roles:manage`, or nobody could grant it back.



## OpenAPI

````yaml /api-reference/openapi.json put /v1/organization/roles/{id}
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/organization/roles/{id}:
    put:
      tags:
        - Roles
      summary: Replace a role's permissions
      description: >-
        Requires `roles:manage`. The permission set is **replaced**, not merged
        — the editor sends the full set, and a merge would make removing a
        permission impossible.


        A system role's permissions can be edited (that is how an organization
        tailors `admin` to itself) but its name cannot: the seeding code and the
        last-owner guard match on `owner` by name. The owner role must keep
        `roles:manage`, or nobody could grant it back.
      operationId: updateRole
      parameters:
        - $ref: '#/components/parameters/OrganizationHeader'
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        $ref: '#/components/requestBodies/Role'
      responses:
        '204':
          description: Updated.
        '400':
          description: '`invalid_request` or `unknown_permission`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            `forbidden`, `owner_locked` (the owner role must keep
            `roles:manage`) or `system_role` (system roles cannot be renamed).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: '`role_exists`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
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.
  requestBodies:
    Role:
      required: true
      content:
        application/json:
          schema:
            type: object
            required:
              - name
              - permissions
            properties:
              name:
                type: string
              permissions:
                type: array
                items:
                  $ref: '#/components/schemas/Permission'
                description: >-
                  The complete set. Duplicates are collapsed; an unknown value
                  is refused.
          example:
            name: Support
            permissions:
              - members:read
              - customers:read
              - customers:write
  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
    Permission:
      type: string
      description: >-
        A fine-grained capability. The catalogue lives in Go; which role holds
        which lives in the database.
      enum:
        - organization:update
        - organization:delete
        - members:read
        - members:write
        - roles:read
        - roles:manage
        - api_keys:read
        - api_keys:write
        - customers:read
        - customers:write
  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'
    NotFound:
      description: >-
        `not_found`. Also the answer for a resource that exists in another
        organization or environment — the 404 never reveals which.
      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.

````