openapi: 3.1.0
info:
  title: Agility Credit SCIM API
  description: |
    SCIM 2.0 endpoints for provisioning users and groups in Agility Credit.

    These endpoints implement the [SCIM 2.0 protocol](https://datatracker.ietf.org/doc/html/rfc7644)
    with the same operation surface that Okta's provisioning client uses. They are intended
    for IdPs (Okta, Microsoft Entra ID) and custom provisioning clients — see the
    [SCIM custom integration guide](/sso/scim-provisioning/custom-integration) for the
    behavioural notes that don't fit a machine-readable spec (PATCH normalization rules,
    silent-ignore semantics, virtual groups, tenant-configurable role attribute, etc.).
  contact:
    email: setup@agilitycredit.com
  license:
    name: Proprietary
    url: https://www.agilitycredit.com/terms-and-conditions/
  version: '2.0'
servers:
  - url: https://api.agilitycredit.net/scim/v2/{tenantId}
    description: Production
    variables:
      tenantId:
        default: your-tenant-id
        description: Your Agility tenant ID (visible in the SCIM endpoint URL in the portal).
  - url: https://api.agc-dev.com/scim/v2/{tenantId}
    description: UAT
    variables:
      tenantId:
        default: your-tenant-id
        description: Your Agility tenant ID (visible in the SCIM endpoint URL in the portal).
security:
  - bearerAuth: []
tags:
  - name: Discovery
    description: |
      Capability and schema discovery endpoints. Use these to confirm what optional SCIM
      features Agility supports and to fetch the live User/Group schemas.
  - name: Users
    description: |
      Create, read, update, and deactivate users. `userName` is the unique identifier
      and is **immutable** once the user is created.
  - name: Groups
    description: |
      Read and update group memberships. Groups are **virtual** — they're derived from the
      tenant's role mapping configuration and cannot be created or deleted via SCIM.
paths:
  /ServiceProviderConfig:
    get:
      tags:
        - Discovery
      summary: Get service provider configuration
      description: |
        Advertises which optional SCIM features Agility supports. Mirror these flags in
        your client: `patch=true`, `bulk=false`, `filter=true` (`maxResults=200`),
        `changePassword=false`, `sort=false`, `etag=false`, `groups=true`.
      operationId: getServiceProviderConfig
      responses:
        '200':
          description: Service provider configuration
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/ServiceProviderConfig'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Schemas:
    get:
      tags:
        - Discovery
      summary: List schemas
      description: Returns the User and Group schemas as a SCIM ListResponse.
      operationId: listSchemas
      responses:
        '200':
          description: List of schemas
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/SchemaListResponse'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Schemas/{schemaId}:
    get:
      tags:
        - Discovery
      summary: Get a schema
      description: |
        Fetch the live definition of a single SCIM schema. Use the canonical schema URI
        as the path parameter:
        `urn:ietf:params:scim:schemas:core:2.0:User` or
        `urn:ietf:params:scim:schemas:core:2.0:Group`.
      operationId: getSchema
      parameters:
        - $ref: '#/components/parameters/SchemaId'
      responses:
        '200':
          description: The requested schema
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/Schema'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Users:
    get:
      tags:
        - Users
      summary: List users
      description: |
        Returns the users for the tenant, optionally filtered. Filterable attributes:
        `userName`, `emails.value`, `name.givenName`, `name.familyName`, `externalId`.
        Supported operators: `eq`, `co`, `sw`.

        Pagination parameters (`startIndex`, `count`) are accepted but the endpoint
        returns the full filtered set in one response (capped at `maxResults=200`).
        Unsupported filter attributes are logged and the request still succeeds
        (returning all users).
      operationId: listUsers
      parameters:
        - name: filter
          in: query
          description: SCIM filter expression, e.g. `userName eq "jane@acme.com"`.
          required: false
          schema:
            type: string
          example: userName eq "jane@acme.com"
        - name: startIndex
          in: query
          description: Accepted but not honoured — the full result set is returned.
          required: false
          schema:
            type: integer
            minimum: 1
        - name: count
          in: query
          description: Accepted but not honoured — the full result set is returned (capped at 200).
          required: false
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: List of users
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
              example:
                schemas:
                  - urn:ietf:params:scim:api:messages:2.0:ListResponse
                totalResults: 1
                startIndex: 1
                itemsPerPage: 1
                Resources:
                  - schemas:
                      - urn:ietf:params:scim:schemas:core:2.0:User
                    id: 6f0e9c6a-1b5a-4f0a-8e3b-1a2c3d4e5f60
                    userName: jane@acme.com
                    name:
                      givenName: Jane
                      familyName: Doe
                      formatted: Jane Doe
                    emails:
                      - value: jane@acme.com
                        type: work
                        primary: true
                    roles:
                      - admin
                    active: true
                    meta:
                      resourceType: User
                      location: /scim/v2/<tenant-id>/Users/6f0e9c6a-1b5a-4f0a-8e3b-1a2c3d4e5f60
        '503':
          $ref: '#/components/responses/ScimDisabled'
    post:
      tags:
        - Users
      summary: Create user
      description: |
        Provision a new user. `userName` must be the user's work email and is **immutable**
        once set. Returns:

        - `201 Created` — new user provisioned.
        - `200 OK` — user already existed (or had been deactivated) and was adopted or
          reactivated by SCIM. Treat both as success; retries are idempotent.
      operationId: createUser
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/User'
            example:
              schemas:
                - urn:ietf:params:scim:schemas:core:2.0:User
              userName: jane@acme.com
              name:
                givenName: Jane
                familyName: Doe
              emails:
                - value: jane@acme.com
                  type: work
                  primary: true
              phoneNumbers:
                - value: '+14155550100'
                  type: work
              roles:
                - admin
              active: true
              externalId: acme-hr-1234
      responses:
        '200':
          description: User already existed and was adopted or reactivated.
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/User'
        '201':
          description: User created.
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Users/{userId}:
    parameters:
      - $ref: '#/components/parameters/UserId'
    get:
      tags:
        - Users
      summary: Get user
      description: Returns the user resource. `404` if the user doesn't exist or is deactivated.
      operationId: getUser
      responses:
        '200':
          description: The user
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    put:
      tags:
        - Users
      summary: Replace user
      description: |
        Full-resource replace. Send the complete user representation. `userName` and
        `emails` are immutable — different values are silently ignored. Setting
        `active: false` deactivates the user (equivalent to `DELETE`).
      operationId: replaceUser
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        '200':
          description: User replaced
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    patch:
      tags:
        - Users
      summary: Update user
      description: |
        Partial update via SCIM PATCH. Accepts both pathed and Okta-style no-path
        operations (the server normalises no-path ops into pathed ops). See the
        [custom integration guide](/sso/scim-provisioning/custom-integration#update-user--patch-usersid)
        for the full table of supported paths.
      operationId: patchUser
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/PatchRequest'
            example:
              schemas:
                - urn:ietf:params:scim:api:messages:2.0:PatchOp
              Operations:
                - op: replace
                  path: name.givenName
                  value: Janet
                - op: replace
                  path: active
                  value: false
      responses:
        '200':
          description: User updated
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    delete:
      tags:
        - Users
      summary: Deactivate user
      description: |
        Soft-deletes (deactivates) the user. The record is retained so a subsequent
        `POST /Users` with the same `userName` reactivates the user rather than creating
        a duplicate.
      operationId: deleteUser
      responses:
        '204':
          description: User deactivated.
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Groups:
    get:
      tags:
        - Groups
      summary: List groups
      description: |
        Returns the virtual groups derived from the tenant's role mapping. Optional
        `filter` parameter — only `displayName eq "<name>"` is honoured.
      operationId: listGroups
      parameters:
        - name: filter
          in: query
          description: SCIM filter expression. Only `displayName eq "<name>"` is honoured.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: List of groups
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/GroupListResponse'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    post:
      tags:
        - Groups
      summary: Create group (idempotent)
      description: |
        Agility's groups are virtual. A `displayName` matching a configured IdP group
        returns the existing virtual group with `201`. An unknown name returns `404`.
        No new mapping is created.
      operationId: createGroup
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/Group'
      responses:
        '201':
          description: Existing virtual group.
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
  /Groups/{groupId}:
    parameters:
      - $ref: '#/components/parameters/GroupId'
    get:
      tags:
        - Groups
      summary: Get group
      description: |
        The path parameter is the role name (`admin`, `user`, `sales`, etc.), which is
        the group's `id`. `404` if the role isn't mapped.
      operationId: getGroup
      responses:
        '200':
          description: The group
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/Group'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    put:
      tags:
        - Groups
      summary: Replace group membership
      description: |
        Treats the `members` array as the new full membership set. Users not listed lose
        the role; users newly listed gain it. Only SCIM-provisioned users are touched.
        No-op when the role mapping source is not `idp_groups` (returns `200`).
      operationId: replaceGroup
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/Group'
      responses:
        '200':
          description: Group updated.
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/Group'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    patch:
      tags:
        - Groups
      summary: Patch group membership
      description: |
        Supports `add`, `remove`, and `replace` operations targeting `members`. Okta-style
        targeted remove (`"path": "members[value eq \"<user-id>\"]"`) works as well as the
        unpathed `remove` of the entire `members` collection. `displayName` changes are
        ignored. No-op when the role mapping source is not `idp_groups`.
      operationId: patchGroup
      requestBody:
        required: true
        content:
          application/scim+json:
            schema:
              $ref: '#/components/schemas/PatchRequest'
            example:
              schemas:
                - urn:ietf:params:scim:api:messages:2.0:PatchOp
              Operations:
                - op: add
                  path: members
                  value:
                    - value: 6f0e9c6a-1b5a-4f0a-8e3b-1a2c3d4e5f60
                - op: remove
                  path: members[value eq "a1b2c3d4-..."]
      responses:
        '200':
          description: Group updated.
          content:
            application/scim+json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
    delete:
      tags:
        - Groups
      summary: Clear group membership
      description: |
        Removes the role from every SCIM-provisioned user that holds it, but does not
        remove the underlying mapping. No-op when the role mapping source is not
        `idp_groups` (returns `204`).
      operationId: deleteGroup
      responses:
        '204':
          description: Group membership cleared.
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/ScimDisabled'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Agility API key passed as a bearer token. Create the key in
        **Settings → Account → API Keys** in the portal. Use a dedicated key for SCIM
        so it can be rotated independently.
  parameters:
    UserId:
      name: userId
      in: path
      required: true
      description: Server-assigned user id, returned in the `id` field on creation.
      schema:
        type: string
    GroupId:
      name: groupId
      in: path
      required: true
      description: |
        The role name (`admin`, `user`, `sales`, `sales_with_credit`, `api`, `accounting`).
      schema:
        type: string
    SchemaId:
      name: schemaId
      in: path
      required: true
      description: The canonical SCIM schema URI.
      schema:
        type: string
        enum:
          - urn:ietf:params:scim:schemas:core:2.0:User
          - urn:ietf:params:scim:schemas:core:2.0:Group
  responses:
    BadRequest:
      description: Payload validation failed.
      content:
        application/scim+json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            schemas:
              - urn:ietf:params:scim:api:messages:2.0:Error
            status: '400'
            scimType: invalidValue
            detail: userName or emails[0].value is required
    NotFound:
      description: Resource not found.
      content:
        application/scim+json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            schemas:
              - urn:ietf:params:scim:api:messages:2.0:Error
            status: '404'
            detail: User not found
    ScimDisabled:
      description: SCIM provisioning is disabled on the tenant.
      content:
        application/scim+json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            schemas:
              - urn:ietf:params:scim:api:messages:2.0:Error
            status: '503'
            detail: SCIM provisioning is disabled for this tenant
  schemas:
    User:
      type: object
      required:
        - schemas
        - userName
        - name
        - active
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:schemas:core:2.0:User
        id:
          type: string
          readOnly: true
          description: Server-assigned UUID. Use it for all subsequent operations.
        externalId:
          type: string
          description: Client-assigned correlation ID. Stored verbatim.
        userName:
          type: string
          format: email
          description: |
            The user's work email. **Immutable** after creation — attempts to change it
            via PUT/PATCH are silently ignored.
        name:
          $ref: '#/components/schemas/Name'
        emails:
          type: array
          description: |
            Primary work email. The `value` must match `userName` and is immutable.
          items:
            $ref: '#/components/schemas/Email'
        phoneNumbers:
          type: array
          description: Phone numbers. `value` must be E.164 (e.g. `+14155550100`).
          items:
            $ref: '#/components/schemas/PhoneNumber'
        roles:
          type: array
          description: |
            Roles assigned to the user. Only honoured when the tenant's role mapping
            source is `idp_attribute`. The attribute name is tenant-configurable; `roles`
            is the default.
          items:
            type: string
            enum:
              - admin
              - user
              - sales
              - sales_with_credit
              - api
              - accounting
        active:
          type: boolean
          description: |
            `false` deactivates (soft-delete); `true` reactivates a previously
            deactivated user.
        meta:
          $ref: '#/components/schemas/Meta'
    Name:
      type: object
      required:
        - givenName
        - familyName
      properties:
        givenName:
          type: string
        familyName:
          type: string
        formatted:
          type: string
          readOnly: true
          description: Computed by the server.
    Email:
      type: object
      required:
        - value
      properties:
        value:
          type: string
          format: email
        type:
          type: string
          enum:
            - work
        primary:
          type: boolean
    PhoneNumber:
      type: object
      required:
        - value
      properties:
        value:
          type: string
          description: E.164 phone number, e.g. `+14155550100`.
          example: '+14155550100'
        type:
          type: string
          enum:
            - work
            - mobile
        primary:
          type: boolean
    Group:
      type: object
      required:
        - schemas
        - displayName
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:schemas:core:2.0:Group
        id:
          type: string
          description: The role name (e.g. `admin`). Server-managed.
          readOnly: true
        displayName:
          type: string
          description: |
            The IdP group name as configured in the tenant's role mapping. Cannot be
            changed via SCIM.
        members:
          type: array
          items:
            $ref: '#/components/schemas/GroupMember'
        meta:
          $ref: '#/components/schemas/Meta'
    GroupMember:
      type: object
      required:
        - value
      properties:
        value:
          type: string
          description: The user's `id`.
        display:
          type: string
          readOnly: true
          description: The user's email.
    Meta:
      type: object
      readOnly: true
      properties:
        resourceType:
          type: string
          enum:
            - User
            - Group
            - Schema
            - ServiceProviderConfig
        created:
          type: string
          format: date-time
        lastModified:
          type: string
          format: date-time
        location:
          type: string
    PatchOp:
      type: object
      required:
        - op
      properties:
        op:
          type: string
          enum:
            - add
            - replace
            - remove
        path:
          type: string
          description: |
            SCIM path. Optional for `replace`/`add` when `value` is an object — the
            server normalises Okta-style no-path ops into pathed ops.
        value:
          description: |
            Scalar, object, or array — shape depends on `path`. For role updates this
            also accepts `{ value: "<role>" }` (Okta `SingleAppRoleAssignment`).
    PatchRequest:
      type: object
      required:
        - schemas
        - Operations
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:api:messages:2.0:PatchOp
        Operations:
          type: array
          items:
            $ref: '#/components/schemas/PatchOp'
    ListResponseBase:
      type: object
      required:
        - schemas
        - totalResults
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:api:messages:2.0:ListResponse
        totalResults:
          type: integer
        startIndex:
          type: integer
        itemsPerPage:
          type: integer
    UserListResponse:
      allOf:
        - $ref: '#/components/schemas/ListResponseBase'
        - type: object
          properties:
            Resources:
              type: array
              items:
                $ref: '#/components/schemas/User'
    GroupListResponse:
      allOf:
        - $ref: '#/components/schemas/ListResponseBase'
        - type: object
          properties:
            Resources:
              type: array
              items:
                $ref: '#/components/schemas/Group'
    SchemaListResponse:
      allOf:
        - $ref: '#/components/schemas/ListResponseBase'
        - type: object
          properties:
            Resources:
              type: array
              items:
                $ref: '#/components/schemas/Schema'
    Schema:
      type: object
      description: SCIM schema definition. See the live `/Schemas/{schemaId}` response for the full attribute list.
      properties:
        schemas:
          type: array
          items:
            type: string
        id:
          type: string
        name:
          type: string
        description:
          type: string
        attributes:
          type: array
          items:
            type: object
        meta:
          $ref: '#/components/schemas/Meta'
    ServiceProviderConfig:
      type: object
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig
        patch:
          type: object
          properties:
            supported:
              type: boolean
        bulk:
          type: object
          properties:
            supported:
              type: boolean
            maxOperations:
              type: integer
            maxPayloadSize:
              type: integer
        filter:
          type: object
          properties:
            supported:
              type: boolean
            maxResults:
              type: integer
        changePassword:
          type: object
          properties:
            supported:
              type: boolean
        sort:
          type: object
          properties:
            supported:
              type: boolean
        etag:
          type: object
          properties:
            supported:
              type: boolean
        groups:
          type: object
          properties:
            supported:
              type: boolean
        authenticationSchemes:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
              name:
                type: string
              description:
                type: string
              specUri:
                type: string
              primary:
                type: boolean
        meta:
          $ref: '#/components/schemas/Meta'
    Error:
      type: object
      required:
        - schemas
        - status
        - detail
      properties:
        schemas:
          type: array
          items:
            type: string
          example:
            - urn:ietf:params:scim:api:messages:2.0:Error
        status:
          type: string
          description: HTTP status code as a string.
        scimType:
          type: string
          description: |
            SCIM error type (e.g. `invalidValue`). Set on 400 responses for schema/value
            problems.
        detail:
          type: string
          description: Human-readable description of the error.
