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

# Add a signer to a customer's wallets

> Adds a new signer to the customer's wallet infrastructure. An add to a live (already-active) roster typically needs one admin approval — see [Multi-signer wallets](/concepts/multi-signer-wallets). The synchronous response returns a `verificationUrl` whose audience is given by `urlAudience`: a passkey signer gets their own enrollment link (`urlAudience: "signer"`, also delivered via the `wallet_signer.invited` webhook) — deliver it to the signer. For a passkey signer added to a live roster this `"signer"` link is the FIRST of two steps, not full self-service: once the signer enrolls their passkey the add parks a roster-add ceremony for a customer admin's co-stamp, and that admin link arrives ONLY on the `wallet_ceremony.awaiting_admin_approval` webhook (carrying the ceremony's `ceremonyId`), never in this response. An api_key signer that parks for admin approval instead returns the admin co-stamp link inline (`urlAudience: "admin"`, also delivered via the `wallet_ceremony.awaiting_admin_approval` webhook) — deliver it to an admin; a synchronously-admitted api_key signer returns null/null. A parked api_key add whose activity is still resolving at the custody provider ALSO returns null/null, but with `awaitingProvider: true` set — that flag is what distinguishes "nothing to do, fully admitted" from "nothing to do yet, still resolving" until the admin link becomes available (poll `reissue-admin-approval`, which mints it once the provider settles). If the admin webhook is lost, recover the admin link with `POST /wallet-signers/{signerId}/reissue-admin-approval`. When an api_key add parks on a roster that carries an ACTIVE api_key admin, the response ADDS `machineApproval` (a token + `approvalMaterial`) alongside that admin link and webhook: stamp the material with an ACTIVE api_key admin key and POST `{ method: "api_key", credential }` to `/v2/verifications/{token}/complete` (credential is `{ signedBody, stamp, organizationId }` as a nested JSON object; a JSON string of the same is deprecated and will be rejected in a future release) to activate the signer without opening the link — the same stamp-and-approve step every machine governance ceremony uses. Conduit never emails anyone — your platform delivers the link. Route by `urlAudience`, never assume the URL is the signer's.

The same signing-mode roster rules as the claim apply to later adds: api_key signers are only valid under a programmatic signing mode, and api_key admins only under programmatic unattended — otherwise `422 SIGNING_MODE_ROSTER_INVALID`. An api_key signer must include `publicKey`, the compressed public key (33 bytes / 66 hex chars, `02`/`03` prefix) on the curve of its `signatureScheme` — `p256` (the default), `secp256k1`, or `secp256k1_eip191` (an EVM wallet personal-message signature); a key already on an active signer returns `409 API_KEY_PUBLIC_KEY_IN_USE`. See [Programmatic payout signing](/guides/machine-signer-stamping).



## OpenAPI

````yaml https://api.sandbox.conduit.financial/v2/api-docs/openapi.json post /customers/{customerId}/wallet-signers
openapi: 3.0.0
info:
  title: Conduit Sandbox API
  description: >-
    **Sandbox API** — clients integrate against this surface to exercise happy
    and unhappy paths without consuming real KYC/PSP credits or moving real
    money. Customer KYC, banking partners, and crypto custody are stubbed;
    org-level KYB runs against real providers. Simulation endpoints under
    `/v2/sandbox/*` drive specific scenarios.


    Internal and portal endpoints are excluded from this spec.
  version: '2.0'
  contact: {}
servers:
  - url: https://api.sandbox.conduit.financial/v2
    description: Sandbox
  - url: https://api.conduit.financial/v2
    description: Production
security:
  - api-key: []
tags:
  - name: Customers
  - name: Registered Addresses
  - name: Wallets
  - name: Wallet Signers
  - name: Signing Quorum
  - name: Virtual Accounts
  - name: Applications
  - name: RFIs
  - name: Documents
  - name: Verifications
  - name: Signing Requests
  - name: Transactions
  - name: Payouts
  - name: Whitelist Recipients
  - name: Orders
  - name: Asset Chain Pairs
  - name: Quotes
  - name: Webhook Endpoints
  - name: Webhook Deliveries
  - name: Webhook Event Types
  - name: Features
  - name: Customer Onboarding
  - name: Markup
  - name: Sandbox
paths:
  /customers/{customerId}/wallet-signers:
    post:
      tags:
        - Wallet Signers
      summary: Add a signer to a customer's wallets
      description: >-
        Adds a new signer to the customer's wallet infrastructure. An add to a
        live (already-active) roster typically needs one admin approval — see
        [Multi-signer wallets](/concepts/multi-signer-wallets). The synchronous
        response returns a `verificationUrl` whose audience is given by
        `urlAudience`: a passkey signer gets their own enrollment link
        (`urlAudience: "signer"`, also delivered via the `wallet_signer.invited`
        webhook) — deliver it to the signer. For a passkey signer added to a
        live roster this `"signer"` link is the FIRST of two steps, not full
        self-service: once the signer enrolls their passkey the add parks a
        roster-add ceremony for a customer admin's co-stamp, and that admin link
        arrives ONLY on the `wallet_ceremony.awaiting_admin_approval` webhook
        (carrying the ceremony's `ceremonyId`), never in this response. An
        api_key signer that parks for admin approval instead returns the admin
        co-stamp link inline (`urlAudience: "admin"`, also delivered via the
        `wallet_ceremony.awaiting_admin_approval` webhook) — deliver it to an
        admin; a synchronously-admitted api_key signer returns null/null. A
        parked api_key add whose activity is still resolving at the custody
        provider ALSO returns null/null, but with `awaitingProvider: true` set —
        that flag is what distinguishes "nothing to do, fully admitted" from
        "nothing to do yet, still resolving" until the admin link becomes
        available (poll `reissue-admin-approval`, which mints it once the
        provider settles). If the admin webhook is lost, recover the admin link
        with `POST /wallet-signers/{signerId}/reissue-admin-approval`. When an
        api_key add parks on a roster that carries an ACTIVE api_key admin, the
        response ADDS `machineApproval` (a token + `approvalMaterial`) alongside
        that admin link and webhook: stamp the material with an ACTIVE api_key
        admin key and POST `{ method: "api_key", credential }` to
        `/v2/verifications/{token}/complete` (credential is `{ signedBody,
        stamp, organizationId }` as a nested JSON object; a JSON string of the
        same is deprecated and will be rejected in a future release) to activate
        the signer without opening the link — the same stamp-and-approve step
        every machine governance ceremony uses. Conduit never emails anyone —
        your platform delivers the link. Route by `urlAudience`, never assume
        the URL is the signer's.


        The same signing-mode roster rules as the claim apply to later adds:
        api_key signers are only valid under a programmatic signing mode, and
        api_key admins only under programmatic unattended — otherwise `422
        SIGNING_MODE_ROSTER_INVALID`. An api_key signer must include
        `publicKey`, the compressed public key (33 bytes / 66 hex chars,
        `02`/`03` prefix) on the curve of its `signatureScheme` — `p256` (the
        default), `secp256k1`, or `secp256k1_eip191` (an EVM wallet
        personal-message signature); a key already on an active signer returns
        `409 API_KEY_PUBLIC_KEY_IN_USE`. See [Programmatic payout
        signing](/guides/machine-signer-stamping).
      operationId: WalletSignersController_addSigner_v2
      parameters:
        - name: customerId
          required: true
          in: path
          schema:
            type: string
            example: cus_1A2b3C4d5E6f7G8h9I0jKl
        - name: Idempotency-Key
          in: header
          description: >-
            Caller-generated unique key that lets the server safely replay this
            request. The original response is returned for 30 days on any retry
            with the same key from the same organization, with the response
            header `Idempotency-Replayed: true`. Required on every
            state-changing money-moving or resource-creating POST.
          required: true
          schema:
            type: string
            pattern: ^[A-Za-z0-9_.:-]{1,128}$
            minLength: 1
            maxLength: 128
            example: 01J7B3K2X9M8N5P6Q7R8S9T0V1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddWalletSignerDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddWalletSignerResponseDto'
        '400':
          description: >-
            **INVALID_OID_FORMAT**: A path or query parameter expected a valid
            object identifier but received a value that does not match the
            expected format.


            **VALIDATION_ERROR**: The request body or query parameters failed
            validation. One or more fields have invalid values, missing required
            properties, or incorrect types. A required text field that holds
            only spaces counts as missing. Multipart file uploads that fail at
            the multipart-parser layer (unexpected form-field name, too many
            parts) carry an extra 'field' member naming the offending
            form-field.


            **MALFORMED_JSON**: The request body could not be parsed as JSON.
            Bodies declared as 'application/json' — and bodies with no
            Content-Type header, which are assumed to be JSON — must contain
            syntactically valid JSON.


            **IDEMPOTENCY_KEY_REQUIRED**: This endpoint requires an
            Idempotency-Key header to prevent duplicate processing. Generate a
            unique key per logical request and resend the request.


            **IDEMPOTENCY_KEY_INVALID**: The Idempotency-Key header value did
            not match the required shape (1-128 characters, letters / digits /
            underscore / dot / colon / hyphen).


            **IDEMPOTENCY_BODY_TOO_NESTED**: The request body exceeds the
            maximum nesting depth allowed by the idempotency fingerprint hasher.
            Deeply-nested arrays or objects are rejected as a malformed payload.


            **API_KEY_PUBLIC_KEY_REQUIRED**: credentialType=api_key requires a
            publicKey (a compressed public key in hex, on the curve of the
            selected signatureScheme).


            **API_KEY_PUBLIC_KEY_INVALID**: The provided `publicKey` is not a
            valid compressed public key on the selected scheme's curve. Expected
            66 hex characters (33 bytes) with a `02` or `03` prefix, on the
            curve of the selected `signatureScheme`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorDto'
              example:
                type: INVALID_OID_FORMAT
                title: Invalid Object ID Format
                status: 400
                detail: >-
                  A path or query parameter expected a valid object identifier
                  but received a value that does not match the expected format.
                resolution: >-
                  Verify that all IDs in the request URL and query parameters
                  are correctly formatted. IDs are typically prefixed strings
                  like 'cus_...', 'app_...', or 'doc_...'.
                docs: https://conduit-v2.mintlify.app/errors#invalid-oid-format
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '401':
          description: >-
            **API_KEY_MISSING**: The request did not include an API key. All API
            requests must be authenticated.


            **API_KEY_INVALID**: The provided API key is not recognized or has
            been revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: API_KEY_MISSING
                title: API Key Missing
                status: 401
                detail: >-
                  The request did not include an API key. All API requests must
                  be authenticated.
                resolution: >-
                  Include your API key in the 'x-api-key' header with every
                  request.
                docs: https://conduit-v2.mintlify.app/errors#api-key-missing
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '403':
          description: >-
            **ORGANIZATION_ACCESS_SUSPENDED**: Conduit has suspended this
            organization's access. Requests with the organization's API keys and
            from its users are refused until Conduit restores access.


            **API_KEY_READ_ONLY**: This API key has read-only access and cannot
            perform write operations. Read-only keys may make read requests
            (GET, HEAD, OPTIONS) only.


            **FEATURE_NOT_ENABLED**: Your account does not have access to this
            feature. Features are enabled on a per-account basis.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: ORGANIZATION_ACCESS_SUSPENDED
                title: Organization Access Suspended
                status: 403
                detail: >-
                  Conduit has suspended this organization's access. Requests
                  with the organization's API keys and from its users are
                  refused until Conduit restores access.
                resolution: Contact Conduit support.
                docs: >-
                  https://conduit-v2.mintlify.app/errors#organization-access-suspended
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '404':
          description: >-
            **CUSTOMER_NOT_FOUND**: No customer exists with the specified ID, or
            the customer belongs to a different organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: CUSTOMER_NOT_FOUND
                title: Customer Not Found
                status: 404
                detail: >-
                  No customer exists with the specified ID, or the customer
                  belongs to a different organization.
                resolution: >-
                  Verify the customer ID is correct. Use the list customers
                  endpoint to find valid customer IDs for your organization.
                docs: https://conduit-v2.mintlify.app/errors#customer-not-found
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '409':
          description: >-
            **IDEMPOTENCY_KEY_CONFLICT**: The idempotency key was previously
            used with a different request body, and that request succeeded or
            ended in a `5xx`. A `4xx` response does not bind the key.
            Idempotency keys are bound to the exact request shape — replays must
            match the original.


            **IDEMPOTENCY_KEY_REQUEST_IN_PROGRESS**: An earlier request with
            this idempotency key has not finished, or stopped before its result
            was recorded. In the second case it may or may not have completed
            its work.


            **API_KEY_PUBLIC_KEY_IN_USE**: The `publicKey` on this add-signer
            request is already held by another active api_key signer on the
            customer's roster. Each machine signer must have a distinct public
            key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: IDEMPOTENCY_KEY_CONFLICT
                title: Idempotency Key Conflict
                status: 409
                detail: >-
                  The idempotency key was previously used with a different
                  request body, and that request succeeded or ended in a `5xx`.
                  A `4xx` response does not bind the key. Idempotency keys are
                  bound to the exact request shape — replays must match the
                  original.
                resolution: >-
                  Use a fresh idempotency key for the new request, or replay the
                  original request unchanged.
                docs: >-
                  https://conduit-v2.mintlify.app/errors#idempotency-key-conflict
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '415':
          description: >-
            **UNSUPPORTED_MEDIA_TYPE**: The request carries a body with a
            Content-Type this endpoint cannot parse. JSON endpoints accept
            'application/json'; a body with no Content-Type header at all is
            assumed to be JSON. File-upload endpoints accept only
            'multipart/form-data' — JSON or undeclared bodies are rejected
            there.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: UNSUPPORTED_MEDIA_TYPE
                title: Unsupported Media Type
                status: 415
                detail: >-
                  The request carries a body with a Content-Type this endpoint
                  cannot parse. JSON endpoints accept 'application/json'; a body
                  with no Content-Type header at all is assumed to be JSON.
                  File-upload endpoints accept only 'multipart/form-data' — JSON
                  or undeclared bodies are rejected there.
                resolution: >-
                  Send the request body with the 'Content-Type:
                  application/json' header. For file uploads, use 'Content-Type:
                  multipart/form-data' — upload endpoints accept no other body
                  type.
                docs: https://conduit-v2.mintlify.app/errors#unsupported-media-type
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '422':
          description: >-
            **SIGNERS_NOT_SUPPORTED**: Signers can only be managed on
            non-custodial wallets. This customer's wallet is custodial, so the
            backend signs on its behalf.


            **INDIVIDUAL_ROSTER_INVALID**: An individual customer's self-managed
            wallet has exactly one admin and at most one person. The person
            signs with passkeys, under the email on file, and is the admin. With
            no person, which only the programmatic unattended signing mode
            allows, the admin is one api_key key. Every other member is an
            api_key signer. After the claim, the roster can only gain api_key
            signers.


            **UNPROCESSABLE_ENTITY**: The request was well-formed but could not
            be processed due to semantic errors or business rule violations.


            **API_KEY_CREDENTIAL_NOT_SUPPORTED**: The request includes a machine
            signer (`credentialType: api_key`) in a context where api_key
            signers cannot be enrolled. This is reserved for future
            compatibility and should not be seen in normal operation.


            **WALLET_NO_PROVIDER_ACCOUNT**: The customer does not yet have a
            wallet account on the underlying provider. Wallet addresses cannot
            be created until the customer claims non-custodial control.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: SIGNERS_NOT_SUPPORTED
                title: Signers Not Supported
                status: 422
                detail: >-
                  Signers can only be managed on non-custodial wallets. This
                  customer's wallet is custodial, so the backend signs on its
                  behalf.
                resolution: >-
                  Claim the wallet as non-custodial via POST
                  /v2/customers/:id/wallets/claim-non-custodial before adding or
                  removing signers.
                docs: https://conduit-v2.mintlify.app/errors#signers-not-supported
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
        '429':
          description: >-
            **RATE_LIMITED**: Too many requests. This error is returned by three
            independent checks: the per-organization bucket applied to every
            authenticated API request; the per-IP bucket applied to
            unauthenticated traffic before an API key is validated; and the
            per-IP bucket applied when repeated invalid API keys are submitted
            from the same address. Honor the Retry-After header (also exposed as
            retryAfterSeconds in the body) before retrying. Current limits and
            remaining budget are visible in X-RateLimit-Limit (the bucket size),
            X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until the
            bucket refills) on every response once your API key is accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitedErrorDto'
              example:
                type: RATE_LIMITED
                title: Rate Limited
                status: 429
                detail: >-
                  Too many requests. This error is returned by three independent
                  checks: the per-organization bucket applied to every
                  authenticated API request; the per-IP bucket applied to
                  unauthenticated traffic before an API key is validated; and
                  the per-IP bucket applied when repeated invalid API keys are
                  submitted from the same address. Honor the Retry-After header
                  (also exposed as retryAfterSeconds in the body) before
                  retrying. Current limits and remaining budget are visible in
                  X-RateLimit-Limit (the bucket size), X-RateLimit-Remaining,
                  and X-RateLimit-Reset (seconds until the bucket refills) on
                  every response once your API key is accepted.
                resolution: >-
                  Sleep until Retry-After seconds have elapsed, then retry. For
                  sustained workloads exceeding the per-organization defaults,
                  request a rate-limit increase through your support contact.
                docs: https://conduit-v2.mintlify.app/errors#rate-limited
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
                retryAfterSeconds: 3
        '500':
          description: >-
            **INTERNAL_ERROR**: An unexpected error occurred while processing
            your request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetailDto'
              example:
                type: INTERNAL_ERROR
                title: Internal Error
                status: 500
                detail: An unexpected error occurred while processing your request.
                resolution: >-
                  Retry the request after a brief delay. If the error persists,
                  contact support and include the correlationId from the error
                  response for investigation.
                docs: https://conduit-v2.mintlify.app/errors#internal-error
                instance: /v2/...
                correlationId: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
                timestamp: '2026-01-15T09:30:00.000Z'
components:
  schemas:
    AddWalletSignerDto:
      type: object
      properties:
        email:
          type: string
          format: email
          pattern: >-
            ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
          description: Email address of the signer to add
          example: signer@example.com
        name:
          description: Display name of the signer
          example: Jane Doe
          type: string
        role:
          description: >-
            admin = root-quorum member + signing-quorum member; signer =
            signing-quorum only
          type: string
          enum:
            - admin
            - signer
        credentialType:
          description: How this signer will stamp. api_key requires `publicKey`.
          type: string
          enum:
            - passkey
            - api_key
        signatureScheme:
          description: >-
            Signature scheme an api_key signer stamps with: `p256` (default when
            omitted), `secp256k1`, or `secp256k1_eip191` (EVM personal_sign).
            Ignored for passkeys. The public key must be a valid point on the
            matching curve.
          type: string
          enum:
            - p256
            - secp256k1
            - secp256k1_eip191
        publicKey:
          description: >-
            Machine-signer compressed public key (33 bytes, 02/03 prefix + 64
            hex chars, optional 0x prefix), on the curve of the selected
            `signatureScheme`. Required when credentialType=api_key.
          example: 02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
          pattern: ^(?:0x)?(?:02|03)[0-9a-fA-F]{64}$
          type: string
      required:
        - email
      additionalProperties: false
    AddWalletSignerResponseDto:
      type: object
      properties:
        id:
          type: string
          pattern: ^wsg_[0-9A-Za-z]{22}$
          description: Unique wallet signer identifier
        email:
          type: string
          description: Signer's email address
          example: signer@example.com
        name:
          type: string
          description: Signer's display name
          example: Jane Doe
          nullable: true
        role:
          description: >-
            admin = root-quorum member + signing-quorum member; signer =
            signing-quorum only
          type: string
          enum:
            - admin
            - signer
        credentialType:
          description: How this signer stamps requests
          type: string
          enum:
            - passkey
            - api_key
        passkeyCount:
          type: integer
          minimum: 0
          maximum: 9007199254740991
          description: Number of passkeys enrolled for this signer
          example: 0
        status:
          description: Current signer status
          type: string
          enum:
            - pending_activation
            - activating
            - active
            - pending_removal
            - removed
            - error
        createdAt:
          type: string
          format: date-time
          description: Timestamp when the signer was created
          example: '2026-01-15T09:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          description: Timestamp when the signer was last updated
          example: '2026-01-15T09:30:00.000Z'
        verificationUrl:
          type: string
          description: >-
            Synchronous URL for this add, or null when none applies. Read
            `urlAudience` to know who it is for before delivering it — a passkey
            add returns the signer's enrollment link, an api_key add pending
            approval returns a customer admin's co-stamp link. On a live roster
            a `"signer"` link is the FIRST of two links, not full self-service:
            after the signer enrolls their passkey the add parks a roster-add
            ceremony for an admin co-stamp, whose link arrives only on the
            `wallet_ceremony.awaiting_admin_approval` webhook (carrying the
            ceremony's `ceremonyId`).
          example: >-
            https://app.conduit.financial/verify/mtQzSU2hdy0DuZ6za2IHIoVDgXzshrFtN38kMULRDFw
          nullable: true
        urlAudience:
          type: string
          enum:
            - signer
            - admin
            - null
          description: >-
            Audience of `verificationUrl`: `signer` = the new signer's
            credential-enrollment link (deliver to the signer) — on a live
            roster this is only the first of two links, the second being an
            admin co-stamp delivered on the
            `wallet_ceremony.awaiting_admin_approval` webhook; `admin` = a
            customer admin's approval link (deliver to an admin; also delivered
            via the `wallet_ceremony.awaiting_admin_approval` webhook); null
            when there is no URL.
          nullable: true
        awaitingProvider:
          description: >-
            True only while the parked activity is still resolving at the
            custody provider — `verificationUrl`/`urlAudience` are null here
            too, but unlike a synchronously-admitted add there is a `ceremonyId`
            to track and nothing yet for any human to approve. Absent on every
            other outcome.
          type: boolean
          enum:
            - true
        machineApproval:
          description: >-
            Additional material for a machine (api_key) add that parks for
            approval on a roster carrying an ACTIVE api_key admin. It is handed
            back ALONGSIDE `verificationUrl` (`urlAudience: "admin"`) and the
            `wallet_ceremony.awaiting_admin_approval` webhook, which are always
            present — approve either way. Stamp `approvalMaterial` with an
            ACTIVE api_key admin's machine key, under the signature scheme that
            admin is enrolled with, and POST `{ method: "api_key", credential }`
            to `/v2/verifications/{token}/complete` (credential is `{
            signedBody, stamp, organizationId }` as a nested JSON object; a JSON
            string of the same is deprecated and will be rejected in a future
            release) to activate the signer — the same stamp-and-approve step
            every machine governance ceremony uses. Absent when the roster has
            no api_key admin to stamp.
          type: object
          properties:
            token:
              type: string
              description: >-
                Verification token. Approve by POSTing the admin stamp to
                `/v2/verifications/{token}/complete` as `{ method: "api_key",
                credential }`, where credential is `{ signedBody, stamp,
                organizationId }` as a nested JSON object (a JSON string of the
                same is deprecated and will be rejected in a future release).
            approvalMaterial:
              type: object
              properties:
                activityId:
                  type: string
                  description: Signing activity id the admin stamp approves.
                fingerprint:
                  type: string
                  description: Activity fingerprint the stamped body must carry.
                subOrganizationId:
                  type: string
                  description: Signing sub-organization the stamp must target.
              required:
                - activityId
                - fingerprint
                - subOrganizationId
          required:
            - token
            - approvalMaterial
      required:
        - id
        - email
        - name
        - role
        - credentialType
        - passkeyCount
        - status
        - createdAt
        - updatedAt
        - verificationUrl
        - urlAudience
    ValidationErrorDto:
      type: object
      properties:
        type:
          type: string
          description: Machine-readable error code
          example: CUSTOMER_NOT_FOUND
        title:
          type: string
          description: Human-readable error type label
          example: Customer Not Found
        status:
          type: number
          description: HTTP status code
          example: 404
        detail:
          type: string
          description: Human-readable explanation of this occurrence
          example: Customer with id cus_034A0gCCVsxdV2PjHLx9k1 not found
        resolution:
          type: string
          description: What the developer should do to resolve this error
          example: >-
            Verify the customer ID. Check you are using the correct API key for
            this organization.
        docs:
          type: string
          description: URL to error documentation
          example: https://conduit-v2.mintlify.app/errors#customer-not-found
        instance:
          type: string
          description: Request path that produced the error
          example: /v2/customers/cus_034A0gCCVsxdV2PjHLx9k1
        correlationId:
          description: Request correlation ID
          example: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
          type: string
        timestamp:
          type: string
          description: ISO 8601 UTC timestamp
          example: '2026-04-27T20:00:00.000Z'
        details:
          description: >-
            Additional structured data for domain-specific errors (e.g. missing
            field lists, pair info)
        errors:
          type: array
          items:
            type: object
            properties:
              pointer:
                type: string
                description: JSON pointer to the invalid field
                example: /email
              detail:
                type: string
                description: What is wrong with this field
                example: Invalid email format
              allowedValues:
                description: The values this field accepts, when it is a closed set
                example:
                  - ach
                  - fedwire
                  - rtp
                type: array
                items:
                  type: string
              category:
                description: >-
                  Class of blocker (requirements-validator output only). 'field'
                  = form-field gap, 'document' = missing or insufficient
                  document (including per-UBO document slots), 'individual' =
                  required person missing.
                example: field
                type: string
                enum:
                  - field
                  - document
                  - individual
            required:
              - pointer
              - detail
      required:
        - type
        - title
        - status
        - detail
        - resolution
        - docs
        - instance
        - timestamp
    ProblemDetailDto:
      type: object
      properties:
        type:
          type: string
          description: Machine-readable error code
          example: CUSTOMER_NOT_FOUND
        title:
          type: string
          description: Human-readable error type label
          example: Customer Not Found
        status:
          type: number
          description: HTTP status code
          example: 404
        detail:
          type: string
          description: Human-readable explanation of this occurrence
          example: Customer with id cus_034A0gCCVsxdV2PjHLx9k1 not found
        resolution:
          type: string
          description: What the developer should do to resolve this error
          example: >-
            Verify the customer ID. Check you are using the correct API key for
            this organization.
        docs:
          type: string
          description: URL to error documentation
          example: https://conduit-v2.mintlify.app/errors#customer-not-found
        instance:
          type: string
          description: Request path that produced the error
          example: /v2/customers/cus_034A0gCCVsxdV2PjHLx9k1
        correlationId:
          description: Request correlation ID
          example: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
          type: string
        timestamp:
          type: string
          description: ISO 8601 UTC timestamp
          example: '2026-04-27T20:00:00.000Z'
        details:
          description: >-
            Additional structured data for domain-specific errors (e.g. missing
            field lists, pair info)
      required:
        - type
        - title
        - status
        - detail
        - resolution
        - docs
        - instance
        - timestamp
    RateLimitedErrorDto:
      type: object
      properties:
        type:
          type: string
          description: Machine-readable error code
          example: CUSTOMER_NOT_FOUND
        title:
          type: string
          description: Human-readable error type label
          example: Customer Not Found
        status:
          type: number
          description: HTTP status code
          example: 404
        detail:
          type: string
          description: Human-readable explanation of this occurrence
          example: Customer with id cus_034A0gCCVsxdV2PjHLx9k1 not found
        resolution:
          type: string
          description: What the developer should do to resolve this error
          example: >-
            Verify the customer ID. Check you are using the correct API key for
            this organization.
        docs:
          type: string
          description: URL to error documentation
          example: https://conduit-v2.mintlify.app/errors#customer-not-found
        instance:
          type: string
          description: Request path that produced the error
          example: /v2/customers/cus_034A0gCCVsxdV2PjHLx9k1
        correlationId:
          description: Request correlation ID
          example: 00469ea4-52c1-4ffa-bd05-9f28b236a5fe
          type: string
        timestamp:
          type: string
          description: ISO 8601 UTC timestamp
          example: '2026-04-27T20:00:00.000Z'
        details:
          description: >-
            Additional structured data for domain-specific errors (e.g. missing
            field lists, pair info)
        retryAfterSeconds:
          type: integer
          minimum: 0
          exclusiveMinimum: true
          maximum: 9007199254740991
          description: Seconds to wait before retrying
          example: 3
      required:
        - type
        - title
        - status
        - detail
        - resolution
        - docs
        - instance
        - timestamp
        - retryAfterSeconds
  securitySchemes:
    api-key:
      type: apiKey
      in: header
      name: x-api-key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.