> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Add user to organization

> Invite a user to the organization by email. An invitation email is always
sent and the user joins on accepting it — there is no path that adds
someone without their consent, even when they already have a platform
account. Inviting an existing member, or someone who already has a
pending invitation, is rejected with 409.

Optionally specify groups to add the user to on acceptance.


<Info>
  Requires the IAM action **`workspace:AddUser`**. See [WORKSPACE permissions](/workspace/permissions) for the full list, what each one covers, and an example policy.
</Info>


## OpenAPI

````yaml /api-reference/specs/workspace.yaml post /v1/users
openapi: 3.0.3
info:
  title: Basaltic Workspace API
  version: 1.0.0
  description: >
    Organization management: organizations, accounts, human users, users-only
    groups, and organization policies. Account IAM identities may receive
    explicitly delegated organization policies through this API. Personal
    authentication remains at the IAM endpoint.


    Organization resources are global and are resolved in the authenticated
    organization. Canonical CRNs are
    crn:workspace:::organization/<organization-uuid>/<type>/<name-or-uuid>.
    Organization policies are separate from account policies; shared system
    policies use crn:workspace:::policy/<name>.
  contact:
    name: Basaltic Support
    email: ping@basaltic.sh
  license:
    name: Proprietary
    url: https://basaltic.sh/terms
servers:
  - url: https://workspace.basaltic.sh
    description: Global API endpoint
security:
  - BearerAuth: []
paths:
  /v1/users:
    post:
      tags:
        - Workspace
      summary: Add user to organization
      description: >
        Invite a user to the organization by email. An invitation email is
        always

        sent and the user joins on accepting it — there is no path that adds

        someone without their consent, even when they already have a platform

        account. Inviting an existing member, or someone who already has a

        pending invitation, is rejected with 409.


        Optionally specify groups to add the user to on acceptance.
      operationId: addUser
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserAddRequest'
      responses:
        '201':
          description: Invitation created and emailed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserAddResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: User is already a member or has a pending invitation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - BearerAuth: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: >-
        Optional client-generated key that makes a create replay-safe. Retrying
        a request with the same key returns the original outcome verbatim
        instead of creating a duplicate resource. Reusing a key with a different
        request body is rejected (422); a request whose key is still being
        processed returns 409. Records are honored for 24 hours. Use a UUID or
        similarly unique token.
      required: false
      schema:
        type: string
        maxLength: 255
      example: 550e8400-e29b-41d4-a716-446655440000
  schemas:
    UserAddRequest:
      type: object
      additionalProperties: false
      required:
        - email
      properties:
        email:
          type: string
          format: email
          description: Email of the user to add
          example: john.doe@acme.com
        tags:
          $ref: '#/components/schemas/Tags'
        groups:
          type: array
          description: >-
            Groups to assign when the invitation is accepted. Each reference is
            validated in the caller organization before the invitation is
            created.
          items:
            $ref: '#/components/schemas/GroupReference'
    UserAddResponse:
      type: object
      required:
        - invitation
        - status
      properties:
        invitation:
          $ref: '#/components/schemas/Invitation'
        status:
          type: string
          enum:
            - invited
          description: >
            Always `invited` — adding a user always goes through an invitation
            the

            invitee has to accept, whether or not they already have a platform

            account.
          example: invited
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Error code identifying the type of error
              example: INVALID_INPUT
            message:
              type: string
              description: Human-readable error message
              example: Invalid request parameters
            request_id:
              type: string
              format: uuid
              description: Request ID for debugging
              example: 550e8400-e29b-41d4-a716-446655440000
    Tags:
      type: object
      additionalProperties:
        type: string
      example:
        environment: production
        team: backend
    GroupReference:
      type: string
      description: >-
        Group UUID, immutable name in the authenticated organization, or
        organization-qualified Workspace CRN. Groups contain users only.
      example: >-
        crn:workspace:::organization/550e8400-e29b-41d4-a716-446655440000/group/developers
    Invitation:
      type: object
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
          example: 550e8400-e29b-41d4-a716-446655440000
        email:
          type: string
          format: email
          description: Email address of the invited user
          example: jane.doe@example.com
        groups:
          type: array
          description: Groups the user will be added to upon accepting
          items:
            $ref: '#/components/schemas/GroupSummary'
        invited_by:
          type: object
          readOnly: true
          properties:
            id:
              type: string
              format: uuid
              example: 550e8400-e29b-41d4-a716-446655440000
            name:
              type: string
              example: John Doe
            email:
              type: string
              format: email
              example: john.doe@acme.com
              description: Human inviter email; omitted for machine identities.
            type:
              type: string
              enum:
                - user
                - service_account
                - assumed_role
              description: >-
                Actual actor type. Assumed-role invitations record the session
                UUID in id.
            crn:
              type: string
              description: >-
                Canonical Workspace user CRN or account IAM
                service-account/session CRN captured when invited.
            account_id:
              type: string
              format: uuid
              description: >-
                Owning account for a service-account or assumed-role inviter;
                omitted for a human inviter.
        status:
          type: string
          enum:
            - pending
            - accepted
            - expired
            - cancelled
          readOnly: true
          example: pending
        expires_at:
          type: string
          format: date-time
          readOnly: true
          example: '2026-01-22T09:30:00Z'
        created_at:
          type: string
          format: date-time
          readOnly: true
          example: '2026-01-15T09:30:00Z'
        crn:
          type: string
          readOnly: true
          example: >-
            crn:workspace:::organization/550e8400-e29b-41d4-a716-446655440000/invitation/550e8400-e29b-41d4-a716-446655440002
    GroupSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: a1b2c3d4-e5f6-7890-1234-567890abcdef
        name:
          description: >-
            Resource names must not start with the literal crn: prefix or be
            UUIDs (canonical, compact, braced, or urn:uuid: forms, in either
            case).
          type: string
          example: developers
  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_INPUT
              message: Invalid request parameters
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: Authentication required or token invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: UNAUTHORIZED
              message: Authentication required
              request_id: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: ACCESS_DENIED
              message: You don't have permission to perform this action
              request_id: 550e8400-e29b-41d4-a716-446655440000
    UnprocessableEntity:
      description: |
        The request is well-formed but cannot be processed as sent. On the
        operations that accept `Idempotency-Key` this is the key-reuse case: the
        key was first seen with a different request payload, so replaying the
        stored outcome would answer a question the caller did not ask.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: IDEMPOTENCY_KEY_REUSED
              message: >-
                This Idempotency-Key was already used with a different request
                payload
              request_id: 550e8400-e29b-41d4-a716-446655440000
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INTERNAL_ERROR
              message: An internal error occurred
              request_id: 550e8400-e29b-41d4-a716-446655440000
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        An OAuth 2.0 bearer token, sent as `Authorization: Bearer <token>`.
        This is the recommended way to authenticate.

        Get one by exchanging a service account's access key pair at
        `POST /v1/oauth/token` with `grant_type=client_credentials`. It is the
        standard client-credentials grant, so any OAuth-aware library will
        obtain and refresh it for you.

        ```
        curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
          https://iam.basaltic.sh/v1/oauth/token
        ```

        Tokens last an hour by default. The same access key pair is separately
        your AWS SigV4 credential for the S3-compatible object endpoint, which
        speaks nothing else.

````