From 2f15f4c5488d723fbf4b8e3d174fdb9e224ba618 Mon Sep 17 00:00:00 2001 From: kphurley7 Date: Tue, 14 Jul 2026 12:40:58 -0500 Subject: [PATCH 1/3] Add stablecoin direct mint/burn issuance API surface Adds the public API surface for direct stablecoin issuer operations that shipped in the Grid server (webdev #29077 mint, #29078 burn): register a provider-created stablecoin, list/get stablecoins, create direct mint and burn operations, and list/get the resulting issuer operations. - New paths: /stablecoins, /stablecoins/{id}, /stablecoins/{id}/mints, /stablecoins/{id}/burns, /stablecoins/{id}/operations, /stablecoin-operations/{id}. - New schemas under components/schemas/stablecoins: Stablecoin, StablecoinListResponse, StablecoinRegisterRequest, StablecoinMintRequest, StablecoinBurnRequest, mint/burn source+destination, StablecoinOperation, StablecoinOperation{Type,Status,Destination,ListResponse}, StablecoinFundingInstructions, StablecoinEstimatedDelivery, StablecoinNetwork, Stablecoin{Grid,}OperationsStatus, StablecoinIssuanceStatus. - Adds Error503 (SERVICE_UNAVAILABLE) used by the issuance responses. StablecoinOperation.destination is a single merged StablecoinOperationDestination object (superset of the mint/burn request destinations), NOT a oneOf: a non-discriminated oneOf[Mint,Burn] is ambiguous (a burn destination validates as both variants), so the generated SDK raises "Multiple matches found" on every burn operation response. The merged object deserializes unambiguously. This supersedes the quote-based issuance design in #578 (which is being closed); the shipped server implements direct operations, not quotes. Bundled with redocly; `redocly lint` passes. Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 15862 +++++++--------- openapi.yaml | 15862 +++++++--------- .../components/schemas/errors/Error503.yaml | 26 + .../schemas/stablecoins/Stablecoin.yaml | 74 + .../StablecoinBurnDestination.yaml | 23 + .../stablecoins/StablecoinBurnRequest.yaml | 20 + .../stablecoins/StablecoinBurnSource.yaml | 19 + .../StablecoinEstimatedDelivery.yaml | 8 + .../StablecoinFundingInstructions.yaml | 8 + .../StablecoinGridOperationsStatus.yaml | 8 + .../stablecoins/StablecoinIssuanceStatus.yaml | 8 + .../stablecoins/StablecoinListResponse.yaml | 18 + .../StablecoinMintDestination.yaml | 18 + .../StablecoinMintFundingSource.yaml | 29 + .../stablecoins/StablecoinMintRequest.yaml | 20 + .../stablecoins/StablecoinNetwork.yaml | 4 + .../stablecoins/StablecoinOperation.yaml | 74 + .../StablecoinOperationDestination.yaml | 33 + .../StablecoinOperationListResponse.yaml | 18 + .../StablecoinOperationStatus.yaml | 10 + .../stablecoins/StablecoinOperationType.yaml | 5 + .../StablecoinRegisterRequest.yaml | 20 + openapi/openapi.yaml | 16 +- ...in-operations_{stablecoinOperationId}.yaml | 40 + openapi/paths/stablecoins/stablecoins.yaml | 124 + .../stablecoins_{stablecoinId}.yaml | 40 + .../stablecoins_{stablecoinId}_burns.yaml | 73 + .../stablecoins_{stablecoinId}_mints.yaml | 73 + ...stablecoins_{stablecoinId}_operations.yaml | 56 + 29 files changed, 14461 insertions(+), 18128 deletions(-) create mode 100644 openapi/components/schemas/errors/Error503.yaml create mode 100644 openapi/components/schemas/stablecoins/Stablecoin.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinBurnDestination.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinBurnRequest.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinGridOperationsStatus.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinIssuanceStatus.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinListResponse.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinMintRequest.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinNetwork.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperation.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperationListResponse.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperationStatus.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperationType.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinRegisterRequest.yaml create mode 100644 openapi/paths/stablecoins/stablecoin-operations_{stablecoinOperationId}.yaml create mode 100644 openapi/paths/stablecoins/stablecoins.yaml create mode 100644 openapi/paths/stablecoins/stablecoins_{stablecoinId}.yaml create mode 100644 openapi/paths/stablecoins/stablecoins_{stablecoinId}_burns.yaml create mode 100644 openapi/paths/stablecoins/stablecoins_{stablecoinId}_mints.yaml create mode 100644 openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 00db2cd55..9671b1c24 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -23,8 +23,6 @@ tags: description: Customer management endpoints for creating and updating customer information - name: Contact Verification description: Endpoints for verifying a customer's email and phone via one-time codes. Required only for customers whose payment provider mandates contact verification (e.g. EU customers); other providers return 409. - - name: Strong Customer Authentication - description: Endpoints for authorizing money-movement operations that require Strong Customer Authentication. Relevant only for customers in a region where SCA is required (e.g. EU); customers outside SCA-regulated regions never see an SCA challenge and these endpoints return 409. - name: KYC/KYB Verifications description: Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. - name: Documents @@ -60,7 +58,7 @@ tags: - name: Cards description: Card management endpoints. Issue debit cards against an internal account, freeze / unfreeze, close, manage card funding sources, and list card transactions. - name: Stablecoins - description: Stablecoin issuance endpoints. Link provider accounts, register provider-created stablecoins, create mint/burn quotes, execute them, and track the resulting operations. + description: Stablecoin issuance endpoints. Link provider accounts, register provider-created stablecoins, create direct mint/burn issuer operations, and track operation status. paths: /config: get: @@ -127,18 +125,6 @@ paths: sendFromEmailSenderName: Acme Notifications replyToEmailAddress: support@acme.com logoUrl: https://acme.com/logo.png - cardTokenization2faConfig: - displayName: Acme - logoUrl: https://acme.com/card-email-logo.png - email: - fromAddress: cards@acme.com - fromName: Acme Cards - replyToAddress: support@acme.com - subject: Your Acme card verification code - bodyText: Use this code to finish adding your Acme card to your digital wallet. - sms: - templateSid: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - bodyText: Use this code to finish adding your Acme card to your digital wallet. responses: '200': description: Configuration updated successfully @@ -426,9 +412,9 @@ paths: address: line1: 123 Market Street line2: Suite 400 - city: Seattle - state: WA - postalCode: '98101' + city: San Francisco + state: CA + postalCode: '94105' country: US responses: '201': @@ -688,9 +674,9 @@ paths: - USDC address: line1: 456 Market St - city: Seattle - state: WA - postalCode: '98101' + city: San Francisco + state: CA + postalCode: '94103' country: US businessUpdate: summary: Update business customer example @@ -705,9 +691,9 @@ paths: taxId: EIN-123456789 address: line1: 100 Technology Parkway - city: Bellevue - state: WA - postalCode: '98004' + city: Palo Alto + state: CA + postalCode: '94304' country: US embeddedWalletEmailUpdate: summary: Embedded Wallet email update request (both steps) @@ -1139,97 +1125,113 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose factors are listed or enrolled. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 + /customers/internal-accounts: get: - summary: List enrolled SCA factors + summary: List Customer internal accounts description: | - List the Strong Customer Authentication factors the customer has enrolled. + Retrieve a list of internal accounts with optional filtering parameters. Returns all + internal accounts that match the specified filters. If no filters are provided, returns all internal accounts + (paginated). - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: listScaFactors + Internal accounts are created automatically when a customer is created based on the platform configuration. + operationId: listCustomerInternalAccounts tags: - - Strong Customer Authentication + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by internal accounts associated with a specific customer + required: false + schema: + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: The customer's enrolled SCA factors. - content: - application/json: - schema: - $ref: '#/components/schemas/ScaFactorList' - '401': - description: Unauthorized + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer not found + $ref: '#/components/schemas/InternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Start SCA factor enrollment + /platform/internal-accounts: + get: + summary: List platform internal accounts description: | - Begin enrolling an SCA factor for the customer. Enrollment covers the - explicit, opt-in factors a customer chooses to add — the request body's - `type` selects `TOTP` or `PASSKEY`. Returns the factor-specific material - needed to finish via `POST /sca/factors/confirm`. - - `SMS_OTP` is implicit and is not enrolled through this endpoint. Every - customer in an SCA-regulated region has a verified phone number from - customer creation (via the Contact Verification flows — - `POST /customers/{customerId}/verify-phone` and `.../verify-phone/confirm`), - so SMS is always available as a factor with no extra setup and appears - among the customer's enrolled factors in `GET /sca/factors`. - - A customer may have **only one passkey**. Starting a passkey enrollment when - one is already enrolled returns `409` (`PASSKEY_ALREADY_ENROLLED`) — delete it - via `DELETE /sca/factors/{credentialId}` first. + Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startScaFactorEnrollment + These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. + operationId: listPlatformInternalAccounts tags: - - Strong Customer Authentication + - Internal Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaFactorEnrollRequestOneOf' + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' responses: '200': - description: Enrollment started; the factor-specific completion material is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ScaFactorEnrollStartOneOf' + $ref: '#/components/schemas/PlatformInternalAccountListResponse' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1240,52 +1242,85 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer (`CONFLICT`), or a passkey enrollment was requested while one is already enrolled (`PASSKEY_ALREADY_ENROLLED`) — only one passkey per customer is supported. + $ref: '#/components/schemas/Error500' + /customers/external-accounts: + get: + summary: List Customer external accounts + description: | + Retrieve a list of external accounts with optional filtering parameters. Returns all + external accounts that match the specified filters. If no filters are provided, returns all external accounts + (paginated). + + External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. + operationId: listCustomerExternalAccounts + tags: + - External Accounts + security: + - BasicAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by external accounts associated with a specific customer + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/ExternalAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/confirm: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer confirming a factor enrollment. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Confirm SCA factor enrollment - description: | - Finalize the factor enrollment started by `POST /sca/factors`. The request - body is discriminated by `type`: for `TOTP`, submit the shared `secret` from - the start call plus the first `code`; for `PASSKEY`, submit the WebAuthn - `credential` the device produced plus the `origin` it was produced against. - The threaded secret/credential binds the confirmation to its enrollment, so - no separate id is needed. - - A TOTP confirmation returns one-time recovery codes (shown once); a passkey - confirmation returns the enrolled factor. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the TOTP code is always `123456`. - operationId: confirmScaFactorEnrollment + summary: Add a new external account + description: Register a new external bank account for a customer. + operationId: createCustomerExternalAccount tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] requestBody: @@ -1293,16 +1328,48 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScaFactorConfirmRequestOneOf' + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Factor enrolled; the factor-specific result is returned. + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/ScaFactorConfirmResponseOneOf' + $ref: '#/components/schemas/ExternalAccount' '400': - description: Invalid or incorrect confirmation proof + description: Bad request content: application/json: schema: @@ -1313,14 +1380,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: SCA is not required for this customer. + description: Conflict - External account already exists content: application/json: schema: @@ -1331,36 +1392,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/{credentialId}: + /customers/external-accounts/{externalAccountId}: parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose factor is being deleted. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: credentialId + - name: externalAccountId in: path - description: The credential id of the enrolled factor to delete (from the factor's `credentialId`). + description: System-generated unique external account identifier required: true schema: type: string - delete: - summary: Delete an enrolled SCA factor - description: | - Delete an enrolled SCA factor by its credential id. Today only `PASSKEY` - factors carry a `credentialId` and are deletable this way. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: deleteScaFactor + get: + summary: Get customer external account by ID + description: Retrieve a customer external account by its system-generated ID + operationId: getCustomerExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] responses: - '204': - description: Factor deleted; no content is returned. + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -1368,68 +1422,28 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or factor not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/login/start: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer starting an SCA login. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Start an SCA login - description: | - Begin an SCA login for the customer with the chosen factor, opening the - end-user SCA session (an exemption gating read / account access beyond the - per-transaction window). Returns factor-specific material: `SMS_OTP` - dispatches a code and returns a `challengeId` + `expiresAt`; `TOTP` returns - only the factor (the customer reads the code from their app); `PASSKEY` - returns WebAuthn `passkeyOptions`. Complete with - `POST /sca/login/complete`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startScaLogin + delete: + summary: Delete customer external account by ID + description: Delete a customer external account by its system-generated ID + operationId: deleteCustomerExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginStartRequest' responses: - '200': - description: SCA login started; factor-specific material is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginStart' - '400': - description: Invalid or unknown factor - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -1437,63 +1451,60 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/login/complete: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer completing an SCA login. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Complete an SCA login + /platform/external-accounts: + get: + summary: List platform external accounts description: | - Finalize an SCA login by submitting the proof for the started factor - (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for - `PASSKEY`), echoing the `challengeId` for `SMS_OTP`. Returns the - reported session status. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. - In sandbox, the SMS/TOTP code is always `123456`. - operationId: completeScaLogin + These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. + operationId: listPlatformExternalAccounts tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginCompleteRequest' + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: SCA login completed; the session status is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ScaLoginComplete' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': - description: Invalid or expired proof + description: Bad request - Invalid parameters content: application/json: schema: @@ -1504,49 +1515,18 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '423': - description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. - content: - application/json: - schema: - $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/record-event: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer the security event is recorded for. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Record a security event - description: | - Record a client-side security-relevant event for the customer with Grid's risk engine (e.g. a sign-in, a sensitive view), to - feed adaptive-authentication signals. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: recordSecurityEvent + summary: Add a new platform external account + description: Register a new external bank account for the platform. + operationId: createPlatformExternalAccount tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] requestBody: @@ -1554,16 +1534,46 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordSecurityEventRequest' + $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Event recorded; the customer's resulting login-security state is returned (including any lockout). + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/RecordSecurityEventResponse' + $ref: '#/components/schemas/ExternalAccount' '400': - description: Invalid event type + description: Bad request content: application/json: schema: @@ -1574,74 +1584,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: SCA is not required for this customer. + description: Conflict - External account already exists content: application/json: schema: $ref: '#/components/schemas/Error409' - '423': - description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. - content: - application/json: - schema: - $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset: + /platform/external-accounts/{externalAccountId}: parameters: - - name: customerId - in: query - description: The unique identifier of the customer resetting a factor. + - name: externalAccountId + in: path + description: System-generated unique external account identifier required: true schema: type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Start a 2FA reset - description: | - Begin recovering a lost enrolled factor via a liveness-gated, poll-based - flow. Opens the liveness check and returns a `resetId` plus the - opaque liveness handles (`livenessAccessToken` / `verificationLink`) the end - user completes it with. Poll - `GET /sca/factors/reset/{resetId}` until liveness - passes, then call the complete endpoint. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startTwoFactorReset + get: + summary: Get platform external account by ID + description: Retrieve a platform external account by its system-generated ID + operationId: getPlatformExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStartRequest' responses: - '201': - description: Reset initiated; the reset handle and liveness material are returned. - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStart' - '400': - description: Invalid or unknown factor + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -1649,62 +1626,28 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '429': - description: Too many reset attempts. Reset initiation is rate-limited to 5 per 24 hours per customer; retry after the window indicated by `Retry-After`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset/{resetId}: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose reset status is polled. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: resetId - in: path - description: The reset handle returned by the start call. - required: true - schema: - type: string - get: - summary: Get 2FA reset status - description: | - Poll the status of an in-progress 2FA reset until it reaches the liveness-passed value, after which the reset can be completed. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: getTwoFactorResetStatus + delete: + summary: Delete platform external account by ID + description: Delete a platform external account by its system-generated ID + operationId: deletePlatformExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] responses: - '200': - description: The current reset status. - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStatus' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -1712,64 +1655,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or reset not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset/{resetId}/complete: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer completing the reset. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: resetId - in: path - description: The reset handle returned by the start call. - required: true - schema: - type: string + /beneficial-owners: post: - summary: Complete a 2FA reset + summary: Create a beneficial owner description: | - Complete a 2FA reset once liveness has passed, clearing the lost factor so - the customer can re-enroll. - - For an `SMS_OTP` reset, supply the new `mobile` number in the body — completing - the reset enrolls it as the customer's number. Other factors need no body. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: completeTwoFactorReset + Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. + operationId: createBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/TwoFactorResetCompleteRequest' + $ref: '#/components/schemas/BeneficialOwnerCreateRequest' responses: - '204': - description: Reset completed; no content is returned. + '201': + description: Beneficial owner created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwner' '400': - description: Reset not ready (liveness not yet passed) + description: Bad request - Invalid parameters content: application/json: schema: @@ -1781,54 +1702,57 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or reset not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/trust: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being trusted. - required: true - schema: - type: string - post: - summary: Start trusting a beneficiary + get: + summary: List beneficial owners description: | - Begin trusting (whitelisting) an external account so future sends to it can - skip the per-transaction SCA ceremony. Returns the `scaChallenge` to satisfy - (when one is issued). Complete with - `POST /customers/external-accounts/{externalAccountId}/trust/confirm`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startBeneficiaryTrust + Retrieve a list of beneficial owners for a business customer. + operationId: listBeneficialOwners tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] + parameters: + - name: customerId + in: query + description: The business customer ID + required: true + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: Beneficiary trust started; the SCA challenge (if any) is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustStart' + $ref: '#/components/schemas/BeneficialOwnerListResponse' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1839,67 +1763,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/trust/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being trusted. - required: true - schema: - type: string - post: - summary: Confirm trusting a beneficiary - description: | - Finalize trusting a beneficiary (identified by the `externalAccountId` in the - path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or - `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when - one was issued. Returns `trusted: true`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the SMS/TOTP code is always `123456`. - operationId: confirmBeneficiaryTrust + /beneficial-owners/{beneficialOwnerId}: + get: + summary: Get a beneficial owner + description: Retrieve details of a specific beneficial owner by ID. + operationId: getBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' + parameters: + - name: beneficialOwnerId + in: path + description: Beneficial owner ID + required: true + schema: + type: string responses: '200': - description: Beneficiary trusted. - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirm' - '400': - description: Invalid or expired proof + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BeneficialOwner' '401': description: Unauthorized content: @@ -1907,55 +1799,47 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/untrust: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being untrusted. - required: true - schema: - type: string - post: - summary: Start untrusting a beneficiary - description: | - Begin untrusting (removing the trusted mark from) an external account, so - future sends to it are dynamically linked and require the per-transaction SCA - ceremony again. Returns the `scaChallenge` to satisfy (when one is issued). - Complete with - `POST /customers/external-accounts/{externalAccountId}/untrust/confirm`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startBeneficiaryUntrust + patch: + summary: Update a beneficial owner + description: Update details of a specific beneficial owner. Only provided fields are updated. + operationId: updateBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] + parameters: + - name: beneficialOwnerId + in: path + description: Beneficial owner ID + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficiary untrust started; the SCA challenge (if any) is returned. + description: Beneficial owner updated successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustStart' + $ref: '#/components/schemas/BeneficialOwner' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1967,62 +1851,40 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/untrust/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being untrusted. - required: true - schema: - type: string + /documents: post: - summary: Confirm untrusting a beneficiary + summary: Upload a document description: | - Finalize untrusting a beneficiary (identified by the `externalAccountId` in - the path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or - `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when - one was issued. Returns `trusted: false`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. - In sandbox, the SMS/TOTP code is always `123456`. - operationId: confirmBeneficiaryUntrust + Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. + operationId: uploadDocument tags: - - Strong Customer Authentication + - Documents security: - BasicAuth: [] requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' + $ref: '#/components/requestBodies/DocumentUploadRequestBody' responses: - '200': - description: Beneficiary untrusted. + '201': + description: Document uploaded successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirm' + $ref: '#/components/schemas/Document' '400': - description: Invalid or expired proof + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2034,56 +1896,33 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Document holder not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/internal-accounts: get: - summary: List Customer internal accounts + summary: List documents description: | - Retrieve a list of internal accounts with optional filtering parameters. Returns all - internal accounts that match the specified filters. If no filters are provided, returns all internal accounts - (paginated). - - Internal accounts are created automatically when a customer is created based on the platform configuration. - operationId: listCustomerInternalAccounts + Retrieve a list of documents with optional filtering by document holder. + operationId: listDocuments tags: - - Internal Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId + - name: documentHolder in: query - description: Filter by internal accounts associated with a specific customer + description: Filter by document holder ID (Customer or BeneficialOwner) required: false schema: type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2105,7 +1944,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/DocumentListResponse' '400': description: Bad request - Invalid parameters content: @@ -2124,107 +1963,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/internal-accounts: + /documents/{documentId}: get: - summary: List platform internal accounts - description: | - Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - - These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. - operationId: listPlatformInternalAccounts + summary: Get a document by ID + description: Retrieve details and metadata of a specific document by ID. + operationId: getDocument tags: - - Internal Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false + - name: documentId + in: path + description: Document ID + required: true schema: type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/PlatformInternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Document' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts: - get: - summary: List Customer external accounts + put: + summary: Replace a document description: | - Retrieve a list of external accounts with optional filtering parameters. Returns all - external accounts that match the specified filters. If no filters are provided, returns all external accounts - (paginated). - - External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. - operationId: listCustomerExternalAccounts + Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. + operationId: replaceDocument tags: - - External Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by external accounts associated with a specific customer - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false + - name: documentId + in: path + description: Document ID + required: true schema: type: string + requestBody: + $ref: '#/components/requestBodies/DocumentReplaceRequestBody' responses: '200': - description: Successful operation + description: Document replaced successfully content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/Document' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2235,79 +2041,51 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add a new external account - description: Register a new external bank account for a customer. - operationId: createCustomerExternalAccount + delete: + summary: Delete a document + description: | + Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. + operationId: deleteDocument tags: - - External Accounts + - Documents security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + parameters: + - name: documentId + in: path + description: Document ID + required: true + schema: + type: string responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Document deleted successfully '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: Conflict - Document cannot be deleted (already submitted for verification) content: application/json: schema: @@ -2318,60 +2096,110 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get customer external account by ID - description: Retrieve a customer external account by its system-generated ID - operationId: getCustomerExternalAccountById + /verifications: + post: + summary: Submit customer for verification + description: | + Trigger KYC (individual) or KYB (business) verification for a customer. + The response indicates whether all required information has been provided. + If data is missing, the `errors` array describes exactly what needs to be + supplied before verification can proceed. + + Call this endpoint again after resolving errors to re-submit. + + ### What to collect for KYB + + Before submitting a `BUSINESS` customer, collect the following via + `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: + + **Business identifying information** + - Entity full legal name + - Doing Business As (DBA) name, if applicable + - Physical address — principal place of business + - Countries of operation + - Identification number — U.S. taxpayer identification number, or, for a + foreign business without one, alternative government-issued documentation + certifying the existence of the business + + **Ownership and control structure** — collected for **one control person** + (an individual with significant responsibility to control, manage, or + direct the legal entity) **and all beneficial owners** (every individual + who owns 25% or more, directly or indirectly). For each, provide: + - Full name + - Date of birth + - Address + - Identification number: + - U.S. persons — SSN or ITIN + - Non-U.S. persons — one or more of: ITIN, passport (with country of + issuance), alien identification card, or another government-issued + photo ID evidencing nationality or residence + + **Required documents** + - Company formation and existence documents (certificate of incorporation, + articles of association, etc.) + - Proof of ownership and control structure (organization and ownership + chart, shareholder agreements, operating agreements, register of members, + or certification of controlling person and beneficial owners) + - Proof of address dated within the last 3 months (utility bill, bank + statement, lease agreement, or official correspondence) + - Tax ID or equivalent identifying-number documents + - For non-U.S. beneficial owners — passport plus one additional + government-issued ID + operationId: createVerification tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + description: | + Verification status returned. Check `verificationStatus` and `errors` to determine next steps. content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Verification' + examples: + missingInfo: + summary: Verification blocked by missing data + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000001 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_FIELD + field: customer.address.line1 + reason: Business address line 1 is required + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 + type: MISSING_FIELD + field: personalInfo.birthDate + reason: Date of birth is required for beneficial owners + createdAt: '2025-10-03T12:00:00Z' + submitted: + summary: Verification submitted successfully + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: IN_PROGRESS + errors: [] + createdAt: '2025-10-03T12:00:00Z' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete customer external account by ID - description: |- - Delete a customer external account by its system-generated ID. - An account that is currently a trusted beneficiary for SCA cannot be deleted — untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete. - operationId: deleteCustomerExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] - responses: - '204': - description: External account deleted successfully + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2379,42 +2207,39 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Returned with `BENEFICIARY_TRUSTED` when the external account is currently a trusted beneficiary. Untrust it first, then delete. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts: get: - summary: List platform external accounts + summary: List verifications description: | - Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. - - These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. - operationId: listPlatformExternalAccounts + Retrieve a list of verifications with optional filtering by customer ID and status. + operationId: listVerifications tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: currency + - name: customerId in: query - description: Filter by currency code + description: Filter by customer ID required: false schema: type: string + - name: verificationStatus + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/VerificationStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2436,7 +2261,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/VerificationListResponse' '400': description: Bad request - Invalid parameters content: @@ -2455,133 +2280,94 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add a new platform external account - description: Register a new external bank account for the platform. - operationId: createPlatformExternalAccount + /verifications/{verificationId}: + get: + summary: Get a verification + description: Retrieve details of a specific verification by ID. + operationId: getVerification tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + parameters: + - name: verificationId + in: path + description: Verification ID + required: true + schema: + type: string responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Verification' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - External account already exists + '404': + description: Verification not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get platform external account by ID - description: Retrieve a platform external account by its system-generated ID - operationId: getPlatformExternalAccountById + /transfer-in: + post: + summary: Create a transfer-in request + description: | + Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + operationId: createTransferIn tags: - - External Accounts + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + '201': + description: Transfer-in request created successfully content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/TransactionOneOf' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete platform external account by ID - description: Delete a platform external account by its system-generated ID - operationId: deletePlatformExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] - responses: - '204': - description: External account deleted successfully + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2589,7 +2375,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or account not found content: application/json: schema: @@ -2600,29 +2386,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners: + /transfer-out: post: - summary: Create a beneficial owner + summary: Create a transfer-out request description: | - Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. - operationId: createBeneficialOwner + Transfer funds from an internal account to an external account for a specific customer. + operationId: createTransferOut tags: - - KYC/KYB Verifications + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerCreateRequest' + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + amount: 12550 + remittanceInformation: '12345' responses: '201': - description: Beneficial owner created successfully + description: Transfer-out request created successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request - Invalid parameters content: @@ -2636,7 +2442,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Customer or account not found content: application/json: schema: @@ -2647,46 +2453,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /receiver/uma/{receiverUmaAddress}: get: - summary: List beneficial owners + summary: Look up an UMA address for payment description: | - Retrieve a list of beneficial owners for a business customer. - operationId: listBeneficialOwners + Lookup a receiving UMA address to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given UMA address. + operationId: lookupUma tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: customerId - in: query - description: The business customer ID + - name: receiverUmaAddress + in: path + description: UMA address of the intended recipient required: true schema: type: string - - name: limit + - name: senderUmaAddress in: query - description: Maximum number of results to return (default 20, max 100) + description: UMA address of the sender (optional if customerId is provided) required: false schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + type: string + - name: customerId in: query - description: Cursor for pagination (returned from previous request) + description: System ID of the sender (optional if senderUmaAddress is provided) required: false schema: type: string responses: '200': - description: Successful operation + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerListResponse' + $ref: '#/components/schemas/ReceiverUmaLookupResponse' '400': - description: Bad request - Invalid parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -2697,35 +2502,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners/{beneficialOwnerId}: + /receiver/external-account/{accountId}: get: - summary: Get a beneficial owner - description: Retrieve details of a specific beneficial owner by ID. - operationId: getBeneficialOwner + summary: Look up an external account for payment + description: | + Lookup an external account by ID to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. + operationId: lookupExternalAccount tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: beneficialOwnerId + - name: accountId in: path - description: Beneficial owner ID + description: System-generated ID of the external account required: true schema: type: string + example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: '200': - description: Successful operation + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' + '400': + description: Bad request - Missing or invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2733,51 +2577,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update a beneficial owner - description: Update details of a specific beneficial owner. Only provided fields are updated. - operationId: updateBeneficialOwner + /quotes/{quoteId}: + get: + summary: Get quote by ID + description: | + Retrieve a quote by its ID. If the quote has been settled, it will include + the transaction ID. This allows clients to track the full lifecycle of a payment + from quote creation to settlement. + operationId: getQuoteById tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: beneficialOwnerId + - name: quoteId in: path - description: Beneficial owner ID + description: ID of the quote to retrieve required: true schema: type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficial owner updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwner' - '400': - description: Bad request - Invalid parameters + description: Quote retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Quote' '401': description: Unauthorized content: @@ -2785,7 +2633,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Quote not found content: application/json: schema: @@ -2796,29 +2644,125 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents: + /quotes: post: - summary: Upload a document + summary: Create a transfer quote description: | - Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. + Generate a quote for a cross-currency transfer between any combination of accounts + and UMA addresses. This endpoint handles currency exchange and provides the necessary + instructions to execute the transfer. - Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. - operationId: uploadDocument + **Transfer Types Supported:** + - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to UMA**: Transfer from an internal account to an UMA address. + - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. + + **Key Features:** + - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount + - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates + - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + + **Important:** If you are transferring funds in the same currency (no exchange required), + use the `/transfer-in` or `/transfer-out` endpoints instead. + operationId: createQuote tags: - - Documents + - Cross-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: requestBody: - $ref: '#/components/requestBodies/DocumentUploadRequestBody' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuoteRequest' + examples: + accountToAccount: + summary: Account to Account Transfer + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + lockedCurrencySide: SENDING + lockedCurrencyAmount: 10000 + description: Transfer between accounts, either internal or external. + accountToUma: + summary: Account to UMA Address Transfer + value: + lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: UMA_ADDRESS + umaAddress: $receiver@uma.domain.com + currency: EUR + lockedCurrencySide: SENDING + lockedCurrencyAmount: 1000 + description: 'Payment for invoice #1234' + realTimeFundingToSparkWallet: + summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. + value: + source: + sourceType: REALTIME_FUNDING + customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 + currency: USD + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 + lockedCurrencySide: RECEIVING + lockedCurrencyAmount: 10000 + immediatelyExecute: true + description: Bitcoin reward payout! responses: '201': - description: Document uploaded successfully + description: | + Transfer quote created successfully. The response includes exchange rates, + fees, and transfer details. For transfers involving UMA addresses, payment + instructions are also included for execution through banking systems. content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/Quote' + example: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + createdAt: '2025-10-03T12:00:00Z' + expiresAt: '2025-10-03T12:05:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: EUR + name: Euro + symbol: € + decimals: 2 + totalSendingAmount: 10000 + totalReceivingAmount: 9200 + exchangeRate: 0.92 + feesIncluded: 10 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -2829,56 +2773,227 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Document holder not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List documents + '501': + description: Not implemented + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /quotes/{quoteId}/execute: + post: + summary: Execute a quote description: | - Retrieve a list of documents with optional filtering by document holder. - operationId: listDocuments + Execute a quote by its ID. This endpoint initiates the transfer between + the source and destination accounts. + + This endpoint can only be used for quotes with a `source` which is either an internal account, + or has direct pull functionality (e.g. ACH pull with an external account). + + When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, + the request must include a `Grid-Wallet-Signature` header. The header value + is the full Grid wallet signature built over the `payloadToSign` value from + the quote's `paymentInstructions[].accountOrWalletInfo` entry with the + session private key of a verified authentication credential on the source + Embedded Wallet. + + Once executed, the quote cannot be cancelled and the transfer will be processed. + operationId: executeQuote tags: - - Documents + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: documentHolder - in: query - description: Filter by document holder ID (Customer or BeneficialOwner) - required: false + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute schema: type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + - name: Idempotency-Key + in: header required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 + type: string + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + responses: + '200': + description: | + Quote confirmed successfully. The transfer has been initiated and + the quote status has been updated. + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '400': + description: Bad request - Invalid quote ID or quote cannot be confirmed + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Quote already confirmed, expired, or in invalid state + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /transactions: + get: + summary: List transactions + description: | + Retrieve a paginated list of transactions with optional filtering. + The transactions can be filtered by customer ID, platform customer ID, UMA address, + date range, status, and transaction type. + + Card transactions are included and identified by `type: CARD`. In Sandbox this is how + you discover a `CardTransaction` id after simulating an authorization — list the + transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` + to the clearing and return simulate endpoints. + operationId: listTransactions + tags: + - Transactions + security: + - BasicAuth: [] + parameters: + - name: customerId + in: query + description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. + required: false + schema: + type: string + - name: platformCustomerId + in: query + description: Filter by platform-specific customer ID + required: false + schema: + type: string + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) + required: false + schema: + type: string + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 - name: cursor in: query description: Cursor for pagination (returned from previous request) required: false schema: type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/DocumentListResponse' + $ref: '#/components/schemas/TransactionListResponse' '400': description: Bad request - Invalid parameters content: @@ -2897,29 +3012,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents/{documentId}: + /transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string get: - summary: Get a document by ID - description: Retrieve details and metadata of a specific document by ID. - operationId: getDocument + summary: Get transaction by ID + description: Retrieve detailed information about a specific transaction. + operationId: getTransactionById tags: - - Documents + - Transactions security: - BasicAuth: [] - parameters: - - name: documentId - in: path - description: Document ID - required: true - schema: - type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: @@ -2927,7 +3042,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: @@ -2938,33 +3053,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - put: - summary: Replace a document + /transactions/{transactionId}/confirm: + post: + summary: Confirm receipt delivery description: | - Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. - operationId: replaceDocument + Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. + operationId: confirmReceiptDelivery tags: - - Documents + - Transactions security: - BasicAuth: [] parameters: - - name: documentId + - name: transactionId in: path - description: Document ID + description: Unique identifier of the transaction to confirm receipt delivery for required: true schema: type: string requestBody: - $ref: '#/components/requestBodies/DocumentReplaceRequestBody' + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Document replaced successfully + description: Receipt delivery confirmation recorded successfully content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2976,7 +3096,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: @@ -2987,25 +3107,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete a document + /transactions/{transactionId}/approve: + post: + summary: Approve a pending incoming payment description: | - Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. - operationId: deleteDocument + Approve a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously approve payments after async processing. + operationId: approvePendingPayment tags: - - Documents + - Transactions security: - BasicAuth: [] parameters: - - name: documentId + - name: transactionId in: path - description: Document ID + description: Unique identifier of the transaction to approve required: true schema: type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovePaymentRequest' responses: - '204': - description: Document deleted successfully + '200': + description: Payment approved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request - Invalid parameters or payment cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3013,13 +3151,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Document cannot be deleted (already submitted for verification) + description: Conflict - Payment is not in a pending state or has already been processed or timed out. content: application/json: schema: @@ -3030,106 +3168,39 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications: + /transactions/{transactionId}/reject: post: - summary: Submit customer for verification + summary: Reject a pending incoming payment description: | - Trigger KYC (individual) or KYB (business) verification for a customer. - The response indicates whether all required information has been provided. - If data is missing, the `errors` array describes exactly what needs to be - supplied before verification can proceed. - - Call this endpoint again after resolving errors to re-submit. - - ### What to collect for KYB - - Before submitting a `BUSINESS` customer, collect the following via - `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: - - **Business identifying information** - - Entity full legal name - - Doing Business As (DBA) name, if applicable - - Physical address — principal place of business - - Countries of operation - - Identification number — U.S. taxpayer identification number, or, for a - foreign business without one, alternative government-issued documentation - certifying the existence of the business - - **Ownership and control structure** — collected for **one control person** - (an individual with significant responsibility to control, manage, or - direct the legal entity) **and all beneficial owners** (every individual - who owns 25% or more, directly or indirectly). For each, provide: - - Full name - - Date of birth - - Address - - Identification number: - - U.S. persons — SSN or ITIN - - Non-U.S. persons — one or more of: ITIN, passport (with country of - issuance), alien identification card, or another government-issued - photo ID evidencing nationality or residence - - **Required documents** - - Company formation and existence documents (certificate of incorporation, - articles of association, etc.) - - Proof of ownership and control structure (organization and ownership - chart, shareholder agreements, operating agreements, register of members, - or certification of controlling person and beneficial owners) - - Proof of address dated within the last 3 months (utility bill, bank - statement, lease agreement, or official correspondence) - - Tax ID or equivalent identifying-number documents - - For non-U.S. beneficial owners — passport plus one additional - government-issued ID - operationId: createVerification + Reject a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously reject payments after additional processing. + operationId: rejectPendingPayment tags: - - KYC/KYB Verifications + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to reject + required: true + schema: + type: string requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/VerificationRequest' + $ref: '#/components/schemas/RejectPaymentRequest' responses: '200': - description: | - Verification status returned. Check `verificationStatus` and `errors` to determine next steps. + description: Payment rejected successfully content: application/json: schema: - $ref: '#/components/schemas/Verification' - examples: - missingInfo: - summary: Verification blocked by missing data - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000001 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_FIELD - field: customer.address.line1 - reason: Business address line 1 is required - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 - type: MISSING_FIELD - field: personalInfo.birthDate - reason: Date of birth is required for beneficial owners - createdAt: '2025-10-03T12:00:00Z' - submitted: - summary: Verification submitted successfully - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: IN_PROGRESS - errors: [] - createdAt: '2025-10-03T12:00:00Z' + $ref: '#/components/schemas/IncomingTransaction' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid parameters or payment cannot be rejected content: application/json: schema: @@ -3141,61 +3212,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List verifications + /crypto/estimate-withdrawal-fee: + post: + summary: Estimate crypto withdrawal fee description: | - Retrieve a list of verifications with optional filtering by customer ID and status. - operationId: listVerifications + Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. + operationId: estimateCryptoWithdrawalFee tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] - parameters: - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: verificationStatus - in: query - description: Filter by verification status - required: false - schema: - $ref: '#/components/schemas/VerificationStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' + examples: + estimateUSDCWithdrawal: + summary: Estimate fee for USDC withdrawal on Solana + value: + internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + currency: USDC + cryptoNetwork: SOLANA + amount: 1000000 + destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU responses: '200': - description: Successful operation + description: Fee estimation returned successfully content: application/json: schema: - $ref: '#/components/schemas/VerificationListResponse' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' '400': description: Bad request - Invalid parameters content: @@ -3208,43 +3273,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /verifications/{verificationId}: - get: - summary: Get a verification - description: Retrieve details of a specific verification by ID. - operationId: getVerification - tags: - - KYC/KYB Verifications - security: - - BasicAuth: [] - parameters: - - name: verificationId - in: path - description: Verification ID - required: true - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Verification' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' '404': - description: Verification not found + description: Internal account not found content: application/json: schema: @@ -3255,49 +3285,24 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-in: + /sandbox/webhooks/test: post: - summary: Create a transfer-in request - description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. - operationId: createTransferIn + summary: Send a test webhook + description: Send a test webhook to the configured endpoint + operationId: sendTestWebhook tags: - - Same-Currency Transfers + - Sandbox security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 responses: - '201': - description: Transfer-in request created successfully + '200': + description: Webhook delivered successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/TestWebhookResponse' '400': - description: Bad request - Invalid parameters + description: Bad request - Webhook delivery failed content: application/json: schema: @@ -3308,129 +3313,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-out: + /customers/bulk/csv: post: - summary: Create a transfer-out request + summary: Upload customers via CSV file description: | - Transfer funds from an internal account to an external account for a specific customer. - operationId: createTransferOut - tags: - - Same-Currency Transfers - security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 + Upload a CSV file containing customer information for bulk creation. The CSV file should follow + a specific format with required and optional columns based on customer type. + + ### CSV Format + The CSV file should have the following columns: + + Required columns for all customers: + - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) + - platformCustomerId: Your platform's unique identifier for the customer + - customerType: Either "INDIVIDUAL" or "BUSINESS" + + Required columns for individual customers: + - fullName: Individual's full name + - birthDate: Date of birth in YYYY-MM-DD format + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Required columns for business customers: + - businessLegalName: Legal name of the business + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Optional columns for all customers: + - addressLine2: Street address line 2 + - platformAccountId: Your platform's identifier for the bank account + - description: Optional description for the customer + + Optional columns for individual customers: + - email: Customer's email address + + Optional columns for business customers: + - businessRegistrationNumber: Business registration number + - businessTaxId: Tax identification number + + ### Example CSV + ```csv + umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName + john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US + acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US + ``` + + The upload process is asynchronous and will return a job ID that can be used to track progress. + You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. + operationId: uploadCustomersCsv + tags: + - Customers + security: + - BasicAuth: [] requestBody: required: true content: - application/json: + multipart/form-data: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - paymentRail: ACH - amount: 12550 - remittanceInformation: '12345' + type: object + required: + - file + properties: + file: + type: string + format: binary + description: CSV file containing customer information responses: - '201': - description: | - Transfer-out request created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionOneOf' - '400': - description: Bad request - Invalid parameters + '202': + description: CSV upload accepted for processing content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJobAccepted' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/uma/{receiverUmaAddress}: + /customers/bulk/jobs/{jobId}: get: - summary: Look up an UMA address for payment + summary: Get bulk import job status description: | - Lookup a receiving UMA address to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given UMA address. - operationId: lookupUma + Retrieve the current status and results of a bulk customer import job. This endpoint can be used + to track the progress of both CSV uploads. + + The response includes: + - Overall job status + - Progress statistics + - Detailed error information for failed entries + - Completion timestamp when finished + operationId: getBulkCustomerImportJob tags: - - Cross-Currency Transfers + - Customers security: - BasicAuth: [] parameters: - - name: receiverUmaAddress + - name: jobId in: path - description: UMA address of the intended recipient + description: ID of the bulk import job to retrieve required: true schema: type: string - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string responses: '200': - description: Successful lookup - content: - application/json: - schema: - $ref: '#/components/schemas/ReceiverUmaLookupResponse' - '400': - description: Bad request - Missing or invalid parameters + description: Job status retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJob' '401': description: Unauthorized content: @@ -3438,69 +3447,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). + description: Job not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/external-account/{accountId}: - get: - summary: Look up an external account for payment + /invitations: + post: + summary: Create an UMA invitation description: | - Lookup an external account by ID to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. - operationId: lookupExternalAccount + Create an UMA invitation from a given platform customer. + operationId: createInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] - parameters: - - name: accountId - in: path - description: System-generated ID of the external account - required: true - schema: - type: string - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UmaInvitationCreateRequest' responses: - '200': - description: Successful lookup + '201': + description: Invitation created successfully content: application/json: schema: - $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -3511,56 +3493,36 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: External account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}: + /invitations/{invitationCode}: get: - summary: Get quote by ID + summary: Get an UMA invitation by code description: | - Retrieve a quote by its ID. If the quote has been settled, it will include - the transaction ID. This allows clients to track the full lifecycle of a payment - from quote creation to settlement. - operationId: getQuoteById + Retrieve details about an UMA invitation by its invitation code. + operationId: getInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: quoteId + - name: invitationCode in: path - description: ID of the quote to retrieve + description: The code of the invitation to get required: true schema: type: string responses: '200': - description: Quote retrieved successfully + description: Invitation retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '401': description: Unauthorized content: @@ -3568,7 +3530,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Invitation not found content: application/json: schema: @@ -3579,133 +3541,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes: + /invitations/{invitationCode}/claim: post: - summary: Create a transfer quote + summary: Claim an UMA invitation description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. + Claim an UMA invitation by associating it with an invitee UMA address. - **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. - - **Account to UMA**: Transfer from an internal account to an UMA address. - - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - - **Key Features:** - - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount - - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + When an invitation is successfully claimed: + 1. The invitation status changes from PENDING to CLAIMED + 2. The invitee UMA address is associated with the invitation + 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. - operationId: createQuote + This endpoint allows customers to accept invitations sent to them by other UMA customers. + operationId: claimInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + - name: invitationCode + in: path + description: The code of the invitation to claim + required: true schema: type: string - example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/QuoteRequest' - examples: - accountToAccount: - summary: Account to Account Transfer - value: - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - lockedCurrencySide: SENDING - lockedCurrencyAmount: 10000 - description: Transfer between accounts, either internal or external. - accountToUma: - summary: Account to UMA Address Transfer - value: - lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: UMA_ADDRESS - umaAddress: $receiver@uma.domain.com - currency: EUR - lockedCurrencySide: SENDING - lockedCurrencyAmount: 1000 - description: 'Payment for invoice #1234' - realTimeFundingToSparkWallet: - summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. - value: - source: - sourceType: REALTIME_FUNDING - customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 - currency: USD - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 - lockedCurrencySide: RECEIVING - lockedCurrencyAmount: 10000 - immediatelyExecute: true - description: Bitcoin reward payout! + $ref: '#/components/schemas/UmaInvitationClaimRequest' responses: - '201': - description: | - Transfer quote created successfully. The response includes exchange rates, - fees, and transfer details. For transfers involving UMA addresses, payment - instructions are also included for execution through banking systems. - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - example: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - createdAt: '2025-10-03T12:00:00Z' - expiresAt: '2025-10-03T12:05:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: EUR - name: Euro - symbol: € - decimals: 2 - totalSendingAmount: 10000 - totalReceivingAmount: 9200 - exchangeRate: 0.92 - feesIncluded: 10 - platformFeesIncluded: 0 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 - '202': - description: | - Quote created but awaiting Strong Customer Authentication. Returned only for customers whose provider requires SCA (e.g. EU) on a realtime-funding source: the quote has status `PENDING_AUTHORIZATION` and carries an `scaChallenge`, and `paymentInstructions` are **withheld** until the challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once authorized, the quote's `paymentInstructions` are populated. + '200': + description: Invitation claimed successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -3716,155 +3590,89 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Invitation not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /quotes/{quoteId}/execute: + /invitations/{invitationCode}/cancel: post: - summary: Execute a quote + summary: Cancel an UMA invitation description: | - Execute a quote by its ID. This endpoint initiates the transfer between - the source and destination accounts. - - This endpoint can only be used for quotes with a `source` which is either an internal account, - or has direct pull functionality (e.g. ACH pull with an external account). + Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, - the request must include a `Grid-Wallet-Signature` header. The header value - is the full Grid wallet signature built over the `payloadToSign` value from - the quote's `paymentInstructions[].accountOrWalletInfo` entry with the - session private key of a verified authentication credential on the source - Embedded Wallet. + When an invitation is cancelled: + 1. The invitation status changes from PENDING to CANCELLED + 2. The invitation can no longer be claimed + 3. The invitation URL will show as cancelled when accessed - Once executed, the quote cannot be cancelled and the transfer will be processed. - operationId: executeQuote + Only pending invitations can be cancelled. Attempting to cancel an invitation + that is already claimed, expired, or cancelled will result in an error. + operationId: cancelInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: quoteId + - name: invitationCode in: path + description: The code of the invitation to cancel required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteQuoteRequest' responses: '200': - description: | - Quote processed. The outcome depends on whether SCA applies to the customer (currently EU customers): - - - **No SCA required:** the transfer is initiated and the quote status advances (`PROCESSING` / `COMPLETED`). - - - **SCA required:** the transfer is **not** initiated yet. The quote is returned with status `PENDING_AUTHORIZATION` and an `scaChallenge`; release the transfer by authorizing the quote you already hold — `POST /quotes/{quoteId}/authorize` (re-calling `execute` returns 409). A pre-funded send is a single challenge, so one authorization releases it. The challenge (and its SMS code / passkey assertion) only comes into existence once this call initiates the transfer, so the proof is always supplied on the follow-up authorize, never inline on this call. If an SMS code lapses, re-send it via `POST /quotes/{quoteId}/authorize/resend`. + description: Invitation cancelled successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Invalid quote ID or quote cannot be confirmed + description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Quote not found + '403': + description: Forbidden - Only the platform which created the invitation can cancel it content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Quote already confirmed, expired, or in invalid state + $ref: '#/components/schemas/Error403' + '404': + description: Invitation not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}/authorize: - parameters: - - name: quoteId - in: path - description: The unique identifier of the quote whose SCA challenge is being authorized. - required: true - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000006 + /sandbox/send: post: - summary: Authorize a quote's SCA challenge + summary: Simulate sending funds description: | - Satisfy the Strong Customer Authentication challenge carried by a quote in - `PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof. - - This is used for realtime-funding quotes: the quote is returned with an - `scaChallenge` and **without** `paymentInstructions`; once authorized, the - quote advances and its `paymentInstructions` are populated so the customer - can fund the transfer. - - As with all SCA, a quote may require more than one authorization: after - authorizing, if the quote is still `PENDING_AUTHORIZATION` it carries the - next `scaChallenge` — authorize that too, repeating until it advances (see - `ScaChallenge`). - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the SMS code is always `123456`. - operationId: authorizeQuote + Simulate sending funds to the bank account as instructed in the quote. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxSend tags: - - Strong Customer Authentication + - Sandbox security: - BasicAuth: [] requestBody: @@ -3872,16 +3680,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScaAuthorization' + $ref: '#/components/schemas/SandboxSendRequest' responses: '200': - description: Challenge authorized; the updated quote is returned. + description: Funds received successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/OutgoingTransaction' '400': - description: Invalid or expired authorization proof + description: Bad request content: application/json: schema: @@ -3892,176 +3700,178 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer, or the quote has no pending challenge to authorize. Also returned with `SCA_SESSION_REQUIRED` when the customer's SCA login session is missing or expired — re-authenticate the customer, then authorize again. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '423': - description: Returned with `ACCOUNT_LOCKED` when the customer's login is temporarily locked after too many failed authorization attempts. The customer must wait for the lock to expire before authorizing again. + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error423' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when authorization attempts for this challenge happen too frequently (for example, repeated bad codes brute-forcing the OTP). The challenge may be invalidated after too many failed attempts. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + $ref: '#/components/schemas/Error403' + '404': + description: Quote not found content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}/authorize/resend: - parameters: - - name: quoteId - in: path - description: The unique identifier of the quote whose SCA challenge code should be re-sent. - required: true - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000006 + /sandbox/uma/receive: post: - summary: Resend a quote's SCA challenge code + summary: Simulate payment send to test receiving an UMA payment description: | - Re-send the one-time code for a realtime-funding quote in - `PENDING_AUTHORIZATION` status whose `scaChallenge.factor` is `SMS_OTP`. The - existing challenge is reused — no new challenge is issued, and its - `scaChallenge.expiresAt` is **not** extended; once the challenge is past - `expiresAt` it can no longer be authorized. - - Only meaningful for customers in a region where SCA is required (e.g. EU); - a 409 is returned otherwise. `PASSKEY` challenges cannot be re-sent and return 409. - - In sandbox, the code is always `123456`. - operationId: resendQuoteScaCode - tags: - - Strong Customer Authentication + Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxReceive + tags: + - Sandbox security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '204': - description: Code re-sent. + '200': + description: Payment triggered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Sender or receiver not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer, the quote has no pending SMS challenge, or the challenge uses a factor whose code cannot be re-sent (e.g. passkey). + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when codes are re-sent too frequently, to avoid spamming the customer's phone. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + $ref: '#/components/schemas/Error500' + /sandbox/internal-accounts/{accountId}/fund: + post: + summary: Simulate funding an internal account + description: | + Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxFundInternalAccount + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: accountId + in: path + required: true + description: The ID of the internal account to fund + schema: + type: string + example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxFundRequest' + examples: + fundUSDAccount: + summary: Fund USD internal account with $1,000 + value: + amount: 100000 + fundBTCAccount: + summary: Fund BTC internal account with 0.01 BTC + value: + amount: 1000000 + responses: + '200': + description: Internal account funded successfully content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/InternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions: + /uma-providers: get: - summary: List transactions + summary: List available Counterparty Providers description: | - Retrieve a paginated list of transactions with optional filtering. - The transactions can be filtered by customer ID, platform customer ID, UMA address, - date range, status, and transaction type. - - Card transactions are included and identified by `type: CARD`. In Sandbox this is how - you discover a `CardTransaction` id after simulating an authorization — list the - transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` - to the clearing and return simulate endpoints. - operationId: listTransactions + Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. + operationId: getAvailableUmaProviders tags: - - Transactions - security: - - BasicAuth: [] + - Available UMA Providers parameters: - - name: customerId - in: query - description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. - required: false - schema: - type: string - - name: platformCustomerId - in: query - description: Filter by platform-specific customer ID - required: false - schema: - type: string - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference + - in: query + name: countryCode + description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. required: false schema: type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format + example: US + - in: query + name: currencyCode + description: The ISO 4217 currency code to filter providers by supported currency. required: false schema: type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format + example: USD + - in: query + name: hasBlockedProviders + description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. required: false schema: - type: string - format: date-time + type: boolean - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -4087,19 +3897,15 @@ paths: - asc - desc default: desc + security: + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/UmaProviderListResponse' '401': description: Unauthorized content: @@ -4112,77 +3918,113 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string - get: - summary: Get transaction by ID - description: Retrieve detailed information about a specific transaction. - operationId: getTransactionById + /tokens: + post: + summary: Create a new API token + description: Create a new API token to access the Grid APIs. + operationId: createToken tags: - - Transactions + - API Tokens security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '200': - description: Successful operation + '201': + description: API token created successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '401': - description: Unauthorized + $ref: '#/components/schemas/ApiToken' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/confirm: - post: - summary: Confirm receipt delivery + get: + summary: List tokens description: | - Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. - operationId: confirmReceiptDelivery + Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match + the specified filters. If no filters are provided, returns all tokens (paginated). + operationId: listTokens tags: - - Transactions + - API Tokens security: - BasicAuth: [] parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to confirm receipt delivery for - required: true - schema: + - name: name + in: query + description: Filter by name of the token + required: false + schema: + type: string + - name: createdAfter + in: query + description: Filter customers created after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: createdBefore + in: query + description: Filter customers created before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedAfter + in: query + description: Filter customers updated after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedBefore + in: query + description: Filter customers updated before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Receipt delivery confirmation recorded successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/TokenListResponse' '400': description: Bad request - Invalid parameters content: @@ -4195,55 +4037,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/approve: - post: - summary: Approve a pending incoming payment - description: | - Approve a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously approve payments after async processing. - operationId: approvePendingPayment + /tokens/{tokenId}: + parameters: + - name: tokenId + in: path + description: System-generated unique token identifier + required: true + schema: + type: string + get: + summary: Get API token by ID + description: Retrieve an API token by their system-generated ID + operationId: getTokenById tags: - - Transactions + - API Tokens security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to approve - required: true - schema: - type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ApprovePaymentRequest' responses: '200': - description: Payment approved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - Invalid parameters or payment cannot be approved + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ApiToken' '401': description: Unauthorized content: @@ -4251,56 +4073,30 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Token not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/reject: - post: - summary: Reject a pending incoming payment - description: | - Reject a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously reject payments after additional processing. - operationId: rejectPendingPayment + delete: + summary: Delete API token by ID + description: Delete an API token by their system-generated ID + operationId: deleteTokenById tags: - - Transactions + - API Tokens security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to reject - required: true - schema: - type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/RejectPaymentRequest' responses: - '200': - description: Payment rejected successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' + '204': + description: API token deleted successfully '400': - description: Bad request - Invalid parameters or payment cannot be rejected + description: Bad request content: application/json: schema: @@ -4312,124 +4108,208 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Token not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/cancel: - post: - summary: Cancel a bank transfer + /internal-accounts/{id}: + parameters: + - name: id + in: path + description: The id of the internal account to update. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account description: | - Request cancellation of a pending bank transfer — an ACH transfer (push or pull) or a wire — before it has settled, for example a payment or collection initiated outside of the receiving bank's processing window. - Whether a transfer can still be cancelled is determined by the banking partner that is settling it: the request is forwarded to the partner's own cancellation facility, and a transfer that the partner has already processed (or that is otherwise past its cancellation window) cannot be cancelled. Cancellation applies to bank-rail transfers; requests for transaction types that cannot be cancelled are rejected. - operationId: cancelTransaction + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating wallet privacy is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount tags: - - Transactions + - Internal Accounts security: - BasicAuth: [] parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to cancel - required: true + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. schema: type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/CancelTransactionRequest' + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true responses: '200': - description: Cancellation requested successfully + description: Signed retry accepted. Returns the updated internal account. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + status: ACTIVE + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Invalid parameters or the transaction cannot be cancelled + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Internal account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - The transaction has already settled or is otherwise past the point where it can be cancelled. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /crypto/estimate-withdrawal-fee: + /internal-accounts/{id}/export: post: - summary: Estimate crypto withdrawal fee + summary: Export internal account wallet credentials description: | - Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. - operationId: estimateCryptoWithdrawalFee + Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. + + Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): + + 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. + + The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. + operationId: exportInternalAccount tags: - - Cross-Currency Transfers + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the internal account to export. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' + $ref: '#/components/schemas/InternalAccountExportRequest' examples: - estimateUSDCWithdrawal: - summary: Estimate fee for USDC withdrawal on Solana + export: + summary: Export request (both steps) value: - internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - currency: USDC - cryptoNetwork: SOLANA - amount: 1000000 - destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 responses: '200': - description: Fee estimation returned successfully + description: Signed retry accepted. Returns the encrypted wallet credentials. content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' + $ref: '#/components/schemas/InternalAccountExportResponse' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: @@ -4446,118 +4326,237 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/webhooks/test: + /auth/credentials: post: - summary: Send a test webhook - description: Send a test webhook to the configured endpoint - operationId: sendTestWebhook + summary: Create an authentication credential + description: | + Register an authentication credential for an Embedded Wallet customer. + + Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. + + Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. + operationId: createAuthCredential tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' + examples: + emailOtp: + summary: Add an email OTP credential + value: + type: EMAIL_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + smsOtp: + summary: Add an SMS OTP credential + value: + type: SMS_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oauth: + summary: Add an OAuth credential + value: + type: OAUTH + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + passkey: + summary: Add a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: iPhone Face-ID + challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx + attestation: + credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY + clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow + transports: + - internal + - hybrid responses: - '200': - description: Webhook delivered successfully + '201': + description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: - $ref: '#/components/schemas/TestWebhookResponse' + $ref: '#/components/schemas/AuthMethodResponse' + examples: + emailOtp: + summary: Email OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + smsOtp: + summary: SMS OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: SMS_OTP + nickname: '+14155550123' + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + oauth: + summary: OAuth credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + passkey: + summary: Passkey credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + '202': + description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Additional email OTP credential challenge + value: + type: EMAIL_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + smsOtp: + summary: Additional SMS OTP credential challenge + value: + type: SMS_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + passkey: + summary: Additional passkey credential challenge + value: + type: PASSKEY + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Webhook delivery failed + description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/csv: - post: - summary: Upload customers via CSV file - description: | - Upload a CSV file containing customer information for bulk creation. The CSV file should follow - a specific format with required and optional columns based on customer type. - - ### CSV Format - The CSV file should have the following columns: - - Required columns for all customers: - - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) - - platformCustomerId: Your platform's unique identifier for the customer - - customerType: Either "INDIVIDUAL" or "BUSINESS" - - Required columns for individual customers: - - fullName: Individual's full name - - birthDate: Date of birth in YYYY-MM-DD format - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) + get: + summary: List authentication credentials + description: |- + Retrieve all authentication credentials registered on an Embedded Wallet internal account. - Required columns for business customers: - - businessLegalName: Legal name of the business - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Optional columns for all customers: - - addressLine2: Street address line 2 - - platformAccountId: Your platform's identifier for the bank account - - description: Optional description for the customer - - Optional columns for individual customers: - - email: Customer's email address - - Optional columns for business customers: - - businessRegistrationNumber: Business registration number - - businessTaxId: Tax identification number - - ### Example CSV - ```csv - umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName - john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US - acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US - ``` - - The upload process is asynchronous and will return a job ID that can be used to track progress. - You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. - operationId: uploadCustomersCsv + The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. + operationId: listAuthCredentials tags: - - Customers + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: - - file - properties: - file: - type: string - format: binary - description: CSV file containing customer information + parameters: + - name: accountId + in: query + description: Internal account id whose authentication credentials to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '202': - description: CSV upload accepted for processing + '200': + description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJobAccepted' + $ref: '#/components/schemas/AuthCredentialListResponse' + examples: + multipleCredentials: + summary: Internal account with multiple authentication credentials + value: + data: + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:35:00Z' + updatedAt: '2026-04-08T15:35:00Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-09T10:15:00Z' + updatedAt: '2026-04-09T10:15:00Z' + empty: + summary: No credentials registered on the account + value: + data: [] + '400': + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -4570,45 +4569,68 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/jobs/{jobId}: - get: - summary: Get bulk import job status + /auth/credentials/{id}: + delete: + summary: Revoke an authentication credential description: | - Retrieve the current status and results of a bulk customer import job. This endpoint can be used - to track the progress of both CSV uploads. + Revoke an authentication credential on an Embedded Wallet internal account. - The response includes: - - Overall job status - - Progress statistics - - Detailed error information for failed entries - - Completion timestamp when finished - operationId: getBulkCustomerImportJob + Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: + + 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. + operationId: revokeAuthCredential tags: - - Customers + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: jobId + - name: id in: path - description: ID of the bulk import job to retrieve + description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). required: true schema: type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Job status retrieved successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJob' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Authentication credential revoked successfully. + '400': + description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Job not found + description: Authentication credential not found content: application/json: schema: @@ -4619,29 +4641,103 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations: + /auth/credentials/{id}/verify: post: - summary: Create an UMA invitation + summary: Verify an authentication credential description: | - Create an UMA invitation from a given platform customer. - operationId: createInvitation + Complete the verification step for a previously created authentication credential and issue a session. + + For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. + + For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. + + On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. + operationId: verifyAuthCredential tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationCreateRequest' + $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' + examples: + emailOtp: + summary: Verify an email OTP credential (first leg) + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + smsOtp: + summary: Verify an SMS OTP credential (first leg) + value: + type: SMS_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + emailOtpSignedRetry: + summary: Signed retry completing an email OTP verification + description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + oauth: + summary: Verify an OAuth credential + value: + type: OAUTH + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + passkey: + summary: Verify a passkey credential + value: + type: PASSKEY + assertion: + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 + authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA + signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: - '201': - description: Invitation created successfully + '200': + description: Authentication credential verified and session issued content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/AuthSession' + '202': + description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Email OTP verification challenge (sign and retry) + value: + type: EMAIL_OTP + payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4649,96 +4745,106 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Authentication credential not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}: - get: - summary: Get an UMA invitation by code - description: | - Retrieve details about an UMA invitation by its invitation code. - operationId: getInvitation - tags: - - Invitations - security: - - BasicAuth: [] - parameters: - - name: invitationCode - in: path - description: The code of the invitation to get - required: true - schema: - type: string - responses: - '200': - description: Invitation retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/UmaInvitation' - '401': - description: Unauthorized - content: - application/json: + $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. schema: - $ref: '#/components/schemas/Error401' - '404': - description: Invitation not found + type: integer content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/claim: + /auth/credentials/{id}/challenge: post: - summary: Claim an UMA invitation + summary: Re-issue an authentication credential challenge description: | - Claim an UMA invitation by associating it with an invitee UMA address. + Re-issue the challenge for an existing authentication credential. - When an invitation is successfully claimed: - 1. The invitation status changes from PENDING to CLAIMED - 2. The invitee UMA address is associated with the invitation - 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation + For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. - This endpoint allows customers to accept invitations sent to them by other UMA customers. - operationId: claimInvitation + `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + + For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. + operationId: challengeAuthCredential tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: invitationCode + - name: id in: path - description: The code of the invitation to claim + description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). required: true schema: type: string requestBody: - required: true + description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. + required: false content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationClaimRequest' + $ref: '#/components/schemas/AuthCredentialChallengeRequest' + examples: + passkey: + summary: Re-challenge a passkey credential + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + emailOtp: + summary: Re-challenge an email-OTP credential (empty body) + value: {} + smsOtp: + summary: Re-challenge an SMS-OTP credential (empty body) + value: {} responses: '200': - description: Invitation claimed successfully + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/AuthCredentialResponseOneOf' + examples: + emailOtp: + summary: Email OTP challenge re-issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + passkey: + summary: Passkey reauthentication challenge issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4752,51 +4858,57 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Invitation not found + description: Authentication credential not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/cancel: - post: - summary: Cancel an UMA invitation - description: | - Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - - When an invitation is cancelled: - 1. The invitation status changes from PENDING to CANCELLED - 2. The invitation can no longer be claimed - 3. The invitation URL will show as cancelled when accessed + /auth/sessions: + get: + summary: List active sessions + description: |- + Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. - Only pending invitations can be cancelled. Attempting to cancel an invitation - that is already claimed, expired, or cancelled will result in an error. - operationId: cancelInvitation + The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. + operationId: listAuthSessions tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: invitationCode - in: path - description: The code of the invitation to cancel + - name: accountId + in: query + description: Internal account id whose sessions to list. required: true schema: type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Invitation cancelled successfully + description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/SessionListResponse' '400': - description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: @@ -4807,48 +4919,58 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Only the platform which created the invitation can cancel it - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Invitation not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/send: - post: - summary: Simulate sending funds + /auth/sessions/{id}: + delete: + summary: Revoke an authentication session description: | - Simulate sending funds to the bank account as instructed in the quote. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxSend + Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: + + 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. + operationId: revokeAuthSession tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxSendRequest' + parameters: + - name: id + in: path + description: The id of the session to revoke. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Funds received successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Session revoked successfully. '400': description: Bad request content: @@ -4856,19 +4978,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Session not found content: application/json: schema: @@ -4879,30 +4995,87 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/uma/receive: + /auth/sessions/{id}/refresh: post: - summary: Simulate payment send to test receiving an UMA payment + summary: Refresh an authentication session description: | - Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxReceive + Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: + + 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. + + The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. + operationId: refreshAuthSession tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxUmaReceiveRequest' - responses: - '200': - description: Payment triggered successfully - content: - application/json: + parameters: + - name: id + in: path + description: The id of the active session to refresh. + required: true + schema: + type: string + example: Session:019542f5-b3e7-1d02-0000-000000000003 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSessionRefreshRequest' + examples: + refresh: + summary: Refresh an active session + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + responses: + '201': + description: New authentication session created successfully. + content: + application/json: schema: - $ref: '#/components/schemas/IncomingTransaction' + $ref: '#/components/schemas/AuthSession' + examples: + session: + summary: Refreshed authentication session + value: + id: Session:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + expiresAt: '2026-04-08T15:50:00Z' + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Session refresh challenge + value: + payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4910,19 +5083,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Sender or receiver not found + description: Session not found content: application/json: schema: @@ -4933,47 +5100,80 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/internal-accounts/{accountId}/fund: + /auth/delegated-keys: post: - summary: Simulate funding an internal account + summary: Create a delegated signing key description: | - Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxFundInternalAccount + Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. + + Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: accountId - in: path - required: true - description: The ID of the internal account to fund + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. schema: type: string - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxFundRequest' + $ref: '#/components/schemas/DelegatedKeyCreateRequest' examples: - fundUSDAccount: - summary: Fund USD internal account with $1,000 - value: - amount: 100000 - fundBTCAccount: - summary: Fund BTC internal account with 0.01 BTC + create: + summary: Delegate signing to a Grid-custodied key value: - amount: 1000000 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Card payments key responses: - '200': - description: Internal account funded successfully + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. content: application/json: schema: - $ref: '#/components/schemas/InternalAccount' + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Card payments key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' '400': description: Bad request content: @@ -4981,92 +5181,65 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token + '404': + description: Card, card funding source, or Embedded Wallet funding account not found content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Internal account not found + $ref: '#/components/schemas/Error404' + '409': + description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /uma-providers: get: - summary: List available Counterparty Providers - description: | - Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. - operationId: getAvailableUmaProviders + summary: List delegated signing keys + description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. + operationId: listDelegatedKeys tags: - - Available UMA Providers + - Embedded Wallet Auth + security: + - BasicAuth: [] parameters: - - in: query - name: countryCode - description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. - required: false - schema: - type: string - example: US - - in: query - name: currencyCode - description: The ISO 4217 currency code to filter providers by supported currency. - required: false - schema: - type: string - example: USD - - in: query - name: hasBlockedProviders - description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. - required: false - schema: - type: boolean - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + - name: accountId in: query - description: Cursor for pagination (returned from previous request) required: false + description: The id of the internal account whose delegated keys to list. schema: type: string - - name: sortOrder + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: fundingSourceId in: query - description: Order to sort results in required: false + description: The id of the card funding source whose delegated keys to list. schema: type: string - enum: - - asc - - desc - default: desc - security: - - BasicAuth: [] + example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Successful operation + description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/UmaProviderListResponse' + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -5079,28 +5252,117 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens: - post: - summary: Create a new API token - description: Create a new API token to access the Grid APIs. - operationId: createToken + /auth/delegated-keys/{id}: + get: + summary: Get a delegated signing key + description: Retrieve a delegated signing key by its system-generated id. + operationId: getDelegatedKeyById tags: - - API Tokens + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ApiTokenCreateRequest' - responses: - '201': - description: API token created successfully - content: - application/json: + parameters: + - name: id + in: path + description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + responses: + '200': + description: Successful operation + content: + application/json: schema: - $ref: '#/components/schemas/ApiToken' + $ref: '#/components/schemas/DelegatedKey' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Revoke a delegated signing key + description: | + Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. + + The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. + operationId: revokeDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + responses: + '204': + description: Delegated key revoked. The key can no longer authorize signing. + '400': + description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents: + post: + summary: Create an agent + description: | + Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. + operationId: createAgent + tags: + - Agent Management + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentCreateRequest' + responses: + '201': + description: Agent created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AgentCreateResponse' '400': description: Bad request content: @@ -5120,46 +5382,56 @@ paths: schema: $ref: '#/components/schemas/Error500' get: - summary: List tokens - description: | - Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match - the specified filters. If no filters are provided, returns all tokens (paginated). - operationId: listTokens + summary: List agents + description: Retrieve a paginated list of agents for the authenticated platform. + operationId: listAgents tags: - - API Tokens + - Agent Management security: - BasicAuth: [] parameters: - - name: name + - name: customerId in: query - description: Filter by name of the token + description: Filter by customer ID required: false schema: type: string + - name: isPaused + in: query + description: Filter by paused status + required: false + schema: + type: boolean + - name: isConnected + in: query + description: Filter by connection status (whether the device code has been redeemed) + required: false + schema: + type: boolean - name: createdAfter in: query - description: Filter customers created after this timestamp (inclusive) + description: Filter agents created after this timestamp (inclusive) required: false schema: type: string format: date-time - name: createdBefore in: query - description: Filter customers created before this timestamp (inclusive) + description: Filter agents created before this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedAfter in: query - description: Filter customers updated after this timestamp (inclusive) + description: Filter agents updated after this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedBefore in: query - description: Filter customers updated before this timestamp (inclusive) + description: Filter agents updated before this timestamp (inclusive) required: false schema: type: string @@ -5185,7 +5457,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TokenListResponse' + $ref: '#/components/schemas/AgentListResponse' '400': description: Bad request - Invalid parameters content: @@ -5204,178 +5476,184 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens/{tokenId}: - parameters: - - name: tokenId - in: path - description: System-generated unique token identifier - required: true - schema: - type: string + /agents/approvals: get: - summary: Get API token by ID - description: Retrieve an API token by their system-generated ID - operationId: getTokenById + summary: List agent transaction approval requests + description: | + Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: listAgentApprovals tags: - - API Tokens + - Agent Management security: - BasicAuth: [] + parameters: + - name: agentId + in: query + description: Filter by agent ID + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ApiToken' - '401': - description: Unauthorized + $ref: '#/components/schemas/AgentActionListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Token not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete API token by ID - description: Delete an API token by their system-generated ID - operationId: deleteTokenById + /agents/me: + get: + summary: Get current agent + description: | + Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. + operationId: getAgentMe tags: - - API Tokens + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: - '204': - description: API token deleted successfully - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Agent' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Token not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}: + /agents/{agentId}: parameters: - - name: id + - name: agentId in: path - description: The id of the internal account to update. + description: System-generated unique agent identifier required: true schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + get: + summary: Get agent by ID + description: Retrieve an agent by its system-generated ID. + operationId: getAgentById + tags: + - Agent Management + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Agent' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Agent not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' patch: - summary: Update internal account - description: | - Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - - Updating wallet privacy is a two-step signed-retry flow: - - 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. - operationId: updateInternalAccount + summary: Update agent + description: Update an agent's name or paused state. + operationId: updateAgent tags: - - Internal Accounts + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountUpdateRequest' - examples: - updateWalletPrivacy: - summary: Update wallet privacy request (both steps) - value: - privateEnabled: true + $ref: '#/components/schemas/AgentUpdateRequest' responses: '200': - description: Signed retry accepted. Returns the updated internal account. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - examples: - enabled: - summary: Wallet privacy enabled - value: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: EMBEDDED_WALLET - status: ACTIVE - balance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - privateEnabled: true - createdAt: '2026-04-08T15:30:00Z' - updatedAt: '2026-04-08T15:35:02Z' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Agent updated successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Internal account update challenge - value: - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/Agent' '400': description: Bad request content: @@ -5383,13 +5661,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: @@ -5400,69 +5678,65 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}/export: - post: - summary: Export internal account wallet credentials + delete: + summary: Delete agent + description: Permanently delete an agent. Connected agent software will lose access immediately. + operationId: deleteAgent + tags: + - Agent Management + security: + - BasicAuth: [] + responses: + '204': + description: Agent deleted successfully + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Agent not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}/policy: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + patch: + summary: Update agent policy description: | - Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. - - Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): - - 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. - - The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. - operationId: exportInternalAccount + Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. + operationId: updateAgentPolicy tags: - - Internal Accounts + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the internal account to export. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountExportRequest' - examples: - export: - summary: Export request (both steps) - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/AgentPolicyUpdateRequest' responses: '200': - description: Signed retry accepted. Returns the encrypted wallet credentials. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountExportResponse' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Agent policy updated successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/Agent' '400': description: Bad request content: @@ -5470,13 +5744,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: @@ -5487,233 +5761,94 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials: + /agents/{agentId}/device-codes: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string post: - summary: Create an authentication credential + summary: Regenerate a device code description: | - Register an authentication credential for an Embedded Wallet customer. - - Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. - - Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. - operationId: createAuthCredential + Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. + operationId: regenerateAgentDeviceCode tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' - examples: - emailOtp: - summary: Add an email OTP credential - value: - type: EMAIL_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - smsOtp: - summary: Add an SMS OTP credential - value: - type: SMS_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oauth: - summary: Add an OAuth credential - value: - type: OAUTH - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - passkey: - summary: Add a passkey credential - value: - type: PASSKEY - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: iPhone Face-ID - challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx - attestation: - credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY - clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 - attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow - transports: - - internal - - hybrid responses: '201': - description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthMethodResponse' - examples: - emailOtp: - summary: Email OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - smsOtp: - summary: SMS OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: SMS_OTP - nickname: '+14155550123' - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - oauth: - summary: OAuth credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - passkey: - summary: Passkey credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - '202': - description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + description: New device code generated successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Additional email OTP credential challenge - value: - type: EMAIL_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - smsOtp: - summary: Additional SMS OTP credential challenge - value: - type: SMS_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - oauth: - summary: Additional OAuth credential challenge - value: - type: OAUTH - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - passkey: - summary: Additional passkey credential challenge - value: - type: PASSKEY - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/AgentDeviceCode' '400': - description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Agent already has an active connection and cannot regenerate a device code + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List authentication credentials - description: |- - Retrieve all authentication credentials registered on an Embedded Wallet internal account. - - The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. - operationId: listAuthCredentials + /agents/{agentId}/actions/{actionId}/approve: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to approve + required: true + schema: + type: string + post: + summary: Approve an agent action + description: | + Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. + For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: approveAgentAction tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: accountId - in: query - description: Internal account id whose authentication credentials to list. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. + description: Action approved successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialListResponse' - examples: - multipleCredentials: - summary: Internal account with multiple authentication credentials - value: - data: - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:35:00Z' - updatedAt: '2026-04-08T15:35:00Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-09T10:15:00Z' - updatedAt: '2026-04-09T10:15:00Z' - empty: - summary: No credentials registered on the account - value: - data: [] + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Action cannot be approved content: application/json: schema: @@ -5724,352 +5859,270 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Agent or action not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Action is not pending approval or has already been processed + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}: - delete: - summary: Revoke an authentication credential + /agents/{agentId}/actions/{actionId}/reject: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to reject + required: true + schema: + type: string + post: + summary: Reject an agent action description: | - Revoke an authentication credential on an Embedded Wallet internal account. - - Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: - - 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. - operationId: revokeAuthCredential + Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: rejectAgentAction tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/AgentActionRejectRequest' responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. + '200': + description: Action rejected successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Authentication credential revoked successfully. + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + description: Bad request - Action cannot be rejected content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Action is not pending approval or has already been processed + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/verify: - post: - summary: Verify an authentication credential + /agents/device-codes/{code}/status: + parameters: + - name: code + in: path + description: The device code to check + required: true + schema: + type: string + get: + summary: Get device code status description: | - Complete the verification step for a previously created authentication credential and issue a session. - - For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. - In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. - - For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. - - On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. - operationId: verifyAuthCredential + Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. + operationId: getAgentDeviceCodeStatus tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' - examples: - emailOtp: - summary: Verify an email OTP credential (first leg) - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - smsOtp: - summary: Verify an SMS OTP credential (first leg) - value: - type: SMS_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - emailOtpSignedRetry: - summary: Signed retry completing an email OTP verification - description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - oauth: - summary: Verify an OAuth credential - value: - type: OAUTH - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - passkey: - summary: Verify a passkey credential - value: - type: PASSKEY - assertion: - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 - authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA - signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Authentication credential verified and session issued - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - '202': - description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Email OTP verification challenge (sign and retry) - value: - type: EMAIL_OTP - payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' '401': - description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Device code not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/challenge: + /agents/device-codes/{code}/redeem: + parameters: + - name: code + in: path + description: The device code to redeem + required: true + schema: + type: string post: - summary: Re-issue an authentication credential challenge + summary: Redeem device code description: | - Re-issue the challenge for an existing authentication credential. - - For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. - - `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. - - For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. - operationId: challengeAuthCredential + Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. + This endpoint does not require platform authentication — the device code itself serves as proof of authorization. + operationId: redeemAgentDeviceCode tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - requestBody: - description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialChallengeRequest' - examples: - passkey: - summary: Re-challenge a passkey credential - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - emailOtp: - summary: Re-challenge an email-OTP credential (empty body) - value: {} - smsOtp: - summary: Re-challenge an SMS-OTP credential (empty body) - value: {} + - Agent Management + security: [] responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Device code redeemed successfully content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' - examples: - emailOtp: - summary: Email OTP challenge re-issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - passkey: - summary: Passkey reauthentication challenge issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' '400': - description: Bad request + description: Bad request (e.g., code already redeemed or expired) content: application/json: schema: $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Device code not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions: + /agents/me/transactions: get: - summary: List active sessions - description: |- - Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. - - The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. - operationId: listAuthSessions + summary: List agent transactions + description: | + Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. + operationId: agentListTransactions tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: accountId + - name: accountIdentifier in: query - description: Internal account id whose sessions to list. - required: true + description: Filter by account identifier (matches either sender or receiver) + required: false schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/SessionListResponse' + $ref: '#/components/schemas/TransactionListResponse' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Invalid parameters content: application/json: schema: @@ -6086,66 +6139,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}: - delete: - summary: Revoke an authentication session + /agents/me/transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get agent transaction by ID description: | - Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: - - 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. - operationId: revokeAuthSession + Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. + operationId: agentGetTransaction tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the session to revoke. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + - AgentAuth: [] responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Session revoked successfully. - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Session not found + description: Transaction not found content: application/json: schema: @@ -6156,205 +6181,186 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}/refresh: + /agents/me/quotes: post: - summary: Refresh an authentication session + summary: Create a transfer quote description: | - Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: - - 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. - - The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. - operationId: refreshAuthSession + Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. + If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. + operationId: agentCreateQuote tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - description: The id of the active session to refresh. - required: true - schema: - type: string - example: Session:019542f5-b3e7-1d02-0000-000000000003 - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id + - name: Idempotency-Key in: header required: false - description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthSessionRefreshRequest' - examples: - refresh: - summary: Refresh an active session - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/QuoteRequest' responses: '201': - description: New authentication session created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - examples: - session: - summary: Refreshed authentication session - value: - id: Session:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - expiresAt: '2026-04-08T15:50:00Z' - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + description: Transfer quote created successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Session refresh challenge - value: - payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Session not found + '403': + description: Forbidden - Agent policy does not permit this operation content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error403' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys: + /agents/me/quotes/{quoteId}: + parameters: + - name: quoteId + in: path + description: ID of the quote to retrieve + required: true + schema: + type: string + get: + summary: Get agent quote by ID + description: | + Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. + operationId: agentGetQuote + tags: + - Agent Operations + security: + - AgentAuth: [] + responses: + '200': + description: Quote retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/quotes/{quoteId}/execute: + parameters: + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Create a delegated signing key + summary: Execute a quote description: | - Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: - - 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. - - 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. - - The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. - - Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. - operationId: createDelegatedKey + Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. + If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + Once executed, the quote cannot be cancelled. + operationId: agentExecuteQuote tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: Grid-Wallet-Signature + - name: Idempotency-Key in: header required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id + example: + - name: Grid-Wallet-Signature in: header required: false - description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyCreateRequest' - examples: - create: - summary: Delegate signing to a Grid-custodied key - value: - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: Card payments key + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 responses: - '201': - description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKey' - examples: - delegatedKey: - summary: Active delegated key - value: - id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 - nickname: Card payments key - status: ACTIVE - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:42Z' - '202': - description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + '200': + description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Quote cannot be executed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Card, card funding source, or Embedded Wallet funding account not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + description: Conflict - Quote already executed, expired, or in invalid state content: application/json: schema: @@ -6365,42 +6371,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/actions: get: - summary: List delegated signing keys - description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. - operationId: listDelegatedKeys + summary: List agent's own actions + description: | + Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. + operationId: agentListActions tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: accountId + - name: status in: query + description: Filter by action status required: false - description: The id of the internal account whose delegated keys to list. schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - name: fundingSourceId + $ref: '#/components/schemas/AgentActionStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor in: query + description: Cursor for pagination (returned from previous request) required: false - description: The id of the card funding source whose delegated keys to list. schema: type: string - example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyListResponse' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentActionListResponse' '401': description: Unauthorized content: @@ -6413,30 +6422,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys/{id}: + /agents/me/actions/{actionId}: + parameters: + - name: actionId + in: path + description: Unique identifier of the agent action + required: true + schema: + type: string get: - summary: Get a delegated signing key - description: Retrieve a delegated signing key by its system-generated id. - operationId: getDelegatedKeyById + summary: Get an agent action + description: | + Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. + operationId: agentGetAction tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/DelegatedKey' + $ref: '#/components/schemas/AgentAction' '401': description: Unauthorized content: @@ -6444,7 +6453,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Delegated key not found + description: Action not found content: application/json: schema: @@ -6455,42 +6464,69 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Revoke a delegated signing key + /agents/me/transfer-in: + post: + summary: Create a transfer-in description: | - Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. - - The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. - operationId: revokeDelegatedKey + Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. + operationId: agentCreateTransferIn tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). - required: true + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '204': - description: Delegated key revoked. The key can no longer authorize signing. + '201': + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + content: + application/json: + schema: + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Delegated key not found + description: Account not found content: application/json: schema: @@ -6501,31 +6537,50 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents: + /agents/me/transfer-out: post: - summary: Create an agent + summary: Create a transfer-out description: | - Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. - operationId: createAgent + Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + operationId: agentCreateTransferOut tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentCreateRequest' + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + amount: 12550 responses: '201': - description: Agent created successfully + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. content: application/json: schema: - $ref: '#/components/schemas/AgentCreateResponse' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -6536,67 +6591,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/internal-accounts: get: - summary: List agents - description: Retrieve a paginated list of agents for the authenticated platform. - operationId: listAgents + summary: List agent's internal accounts + description: | + Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. + operationId: agentListInternalAccounts tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: isPaused - in: query - description: Filter by paused status - required: false - schema: - type: boolean - - name: isConnected - in: query - description: Filter by connection status (whether the device code has been redeemed) - required: false - schema: - type: boolean - - name: createdAfter - in: query - description: Filter agents created after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: createdBefore - in: query - description: Filter agents created before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedAfter + - name: currency in: query - description: Filter agents updated after this timestamp (inclusive) + description: Filter by currency code required: false schema: type: string - format: date-time - - name: updatedBefore + - name: type in: query - description: Filter agents updated before this timestamp (inclusive) + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. required: false schema: - type: string - format: date-time + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6618,13 +6653,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/InternalAccountListResponse' '401': description: Unauthorized content: @@ -6637,43 +6666,23 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/approvals: + /agents/me/external-accounts: get: - summary: List agent transaction approval requests + summary: List agent external accounts description: | - Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: listAgentApprovals + Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentListExternalAccounts tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: agentId - in: query - description: Filter by agent ID - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate + - name: currency in: query - description: Filter by end date (inclusive) in ISO 8601 format + description: Filter by currency code required: false schema: type: string - format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6689,23 +6698,13 @@ paths: required: false schema: type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -6724,58 +6723,105 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me: - get: - summary: Get current agent + post: + summary: Add an external account description: | - Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. - operationId: getAgentMe + Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. + operationId: agentCreateExternalAccount tags: - Agent Operations security: - AgentAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Successful operation + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': + '409': + description: Conflict - External account already exists + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}: + /agents/me/external-accounts/{externalAccountId}: parameters: - - name: agentId + - name: externalAccountId in: path - description: System-generated unique agent identifier + description: System-generated unique external account identifier required: true schema: type: string get: - summary: Get agent by ID - description: Retrieve an agent by its system-generated ID. - operationId: getAgentById + summary: Get agent external account by ID + description: | + Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentGetExternalAccount tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -6783,7 +6829,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: External account not found content: application/json: schema: @@ -6794,33 +6840,18 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update agent - description: Update an agent's name or paused state. - operationId: updateAgent + delete: + summary: Delete agent external account + description: | + Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentDeleteExternalAccount tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentUpdateRequest' + - AgentAuth: [] responses: - '200': - description: Agent updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -6828,7 +6859,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: External account not found content: application/json: schema: @@ -6839,67 +6870,134 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent - description: Permanently delete an agent. Connected agent software will lose access immediately. - operationId: deleteAgent + /cards: + post: + summary: Issue a card + description: | + Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + + If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. + + New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). + operationId: createCard tags: - - Agent Management + - Cards security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardCreateRequest' + examples: + virtualCard: + summary: Issue a virtual card with one funding source + value: + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '204': - description: Agent deleted successfully - '401': - description: Unauthorized + '201': + description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found + $ref: '#/components/schemas/Card' + '400': + description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/policy: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - patch: - summary: Update agent policy + '501': + description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + get: + summary: List cards description: | - Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. - operationId: updateAgentPolicy + Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. + operationId: listCards tags: - - Agent Management + - Cards security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentPolicyUpdateRequest' + parameters: + - name: cardholderId + in: query + description: Filter by cardholder (customer) id. + required: false + schema: + type: string + - name: accountId + in: query + description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. + required: false + schema: + type: string + - name: platformCardId + in: query + description: Filter by platform-specific card identifier. + required: false + schema: + type: string + - name: state + in: query + description: Filter by card state. + required: false + schema: + $ref: '#/components/schemas/CardState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Agent policy updated successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/CardListResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -6910,48 +7008,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/device-codes: + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}: parameters: - - name: agentId + - name: id in: path - description: System-generated unique agent identifier + description: System-generated unique card identifier required: true schema: type: string - post: - summary: Regenerate a device code - description: | - Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. - operationId: regenerateAgentDeviceCode + get: + summary: Get a card + description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. + operationId: getCardById tags: - - Agent Management + - Cards security: - BasicAuth: [] responses: - '201': - description: New device code generated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentDeviceCode' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Card' '401': description: Unauthorized content: @@ -6959,75 +7050,128 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Card not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Agent already has an active connection and cannot regenerate a device code - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/approve: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to approve - required: true - schema: - type: string - post: - summary: Approve an agent action + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + patch: + summary: Update a card description: | - Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. - For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: approveAgentAction + Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. + + - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. + - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. + + Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): + + 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + + Effects: + - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. + - `state: ACTIVE`: normal authorization behavior resumes. + - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. + - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + + The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + operationId: updateCardById tags: - - Agent Management + - Cards security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardUpdateRequest' + examples: + freeze: + summary: Freeze an active card + value: + state: FROZEN + unfreeze: + summary: Unfreeze a frozen card + value: + state: ACTIVE + updateFundingSources: + summary: Replace the card's bound funding sources + value: + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + freezeAndUpdateSources: + summary: Freeze the card and replace its funding sources in one call + value: + state: FROZEN + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + close: + summary: Permanently close the card + value: + state: CLOSED responses: '200': - description: Action approved successfully. Returns the updated AgentAction. + description: Signed retry accepted. Returns the updated card. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/Card' + '202': + description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request - Action cannot be approved + description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent or action not found + description: Card not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Action is not pending approval or has already been processed + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: @@ -7038,105 +7182,148 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/reject: + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}/reveal: parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId + - name: id in: path - description: Unique identifier of the agent action to reject + description: System-generated unique card identifier required: true schema: type: string post: - summary: Reject an agent action - description: | - Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: rejectAgentAction + summary: Reveal card details + description: |- + Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. + + Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. + + Every reveal is audit-logged with the requesting actor. + operationId: revealCard tags: - - Agent Management + - Cards security: - BasicAuth: [] - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionRejectRequest' responses: '200': - description: Action rejected successfully. Returns the updated AgentAction. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Action cannot be rejected + description: Reveal URL minted. content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/CardRevealResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent or action not found + '403': + description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed + $ref: '#/components/schemas/Error403' + '404': + description: Card not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/status: - parameters: - - name: code - in: path - description: The device code to check - required: true - schema: - type: string - get: - summary: Get device code status + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /sandbox/cards/{id}/simulate/authorization: + post: + summary: Simulate a card authorization description: | - Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. - operationId: getAgentDeviceCodeStatus + Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. + + The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: + + | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | + + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorization tags: - - Agent Management + - Sandbox security: - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate an authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + examples: + coffeeAuth: + summary: Approved $12.50 auth at a coffee shop + value: + amount: 1250 + currency: + code: USD + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + declinedInsufficientFunds: + summary: Declined — insufficient funds (descriptor suffix `002`) + value: + amount: 50000 + currency: + code: USD + merchant: + descriptor: AMAZON RETAIL US-002 + mcc: '5942' + country: US responses: '200': - description: Successful operation + description: Simulated authorization processed. Returns the resulting card transaction. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' + $ref: '#/components/schemas/CardTransaction' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Device code not found + description: Card not found (also returned in production for this path) content: application/json: schema: @@ -7147,38 +7334,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/redeem: - parameters: - - name: code - in: path - description: The device code to redeem - required: true - schema: - type: string + /sandbox/cards/{id}/simulate/clearing: post: - summary: Redeem device code + summary: Simulate a card clearing description: | - Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. - This endpoint does not require platform authentication — the device code itself serves as proof of authorization. - operationId: redeemAgentDeviceCode + Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. + + - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). + - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. + - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). + + Production returns `404` on this path. + operationId: sandboxSimulateCardClearing tags: - - Agent Management - security: [] + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the clearing applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardClearingRequest' + examples: + tipOnTopClearing: + summary: Clearing larger than auth — exercises post-hoc pull + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + authorizationExpiry: + summary: Clearing of 0 — exercises authorization expiry + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 0 responses: '200': - description: Device code redeemed successfully + description: Simulated clearing processed. Returns the updated card transaction. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + $ref: '#/components/schemas/CardTransaction' '400': - description: Bad request (e.g., code already redeemed or expired) + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Device code not found + description: Card or card transaction not found content: application/json: schema: @@ -7189,67 +7412,160 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions: - get: - summary: List agent transactions + /sandbox/cards/{id}/simulate/return: + post: + summary: Simulate a card return description: | - Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. - operationId: agentListTransactions + Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). + + Production returns `404` on this path. + operationId: sandboxSimulateCardReturn tags: - - Agent Operations + - Sandbox security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false + - name: id + in: path + required: true + description: The id of the card the return applies to. schema: type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardReturnRequest' + examples: + fullRefund: + summary: Full refund of a $15.00 settled transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + responses: + '200': + description: Simulated return processed. Returns the updated card transaction. + content: + application/json: + schema: + $ref: '#/components/schemas/CardTransaction' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins: + post: + summary: Register an existing provider-created stablecoin + description: | + Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. + operationId: registerStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this request safely. + required: true schema: type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinRegisterRequest' + responses: + '201': + description: Stablecoin registered + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Referenced stablecoin provider account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoins + description: Retrieve stablecoins registered to the authenticated platform. + operationId: listStablecoins + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: issuanceStatus in: query - description: Filter by reference required: false schema: - type: string - - name: startDate + $ref: '#/components/schemas/StablecoinIssuanceStatus' + - name: gridOperationsStatus in: query - description: Filter by start date (inclusive) in ISO 8601 format required: false schema: - type: string - format: date-time - - name: endDate + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + - name: provider in: query - description: Filter by end date (inclusive) in ISO 8601 format required: false schema: - type: string - format: date-time + $ref: '#/components/schemas/StablecoinProvider' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7261,27 +7577,17 @@ paths: default: 20 - name: cursor in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in + description: Opaque cursor returned as `nextCursor` from the previous response. required: false schema: type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/StablecoinListResponse' '400': description: Bad request - Invalid parameters content: @@ -7300,30 +7606,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions/{transactionId}: + /stablecoins/{stablecoinId}: parameters: - - name: transactionId + - name: stablecoinId in: path - description: Unique identifier of the transaction + description: System-generated stablecoin identifier required: true schema: type: string get: - summary: Get agent transaction by ID - description: | - Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. - operationId: agentGetTransaction + summary: Get a stablecoin + description: Retrieve a single registered stablecoin by its Grid identifier. + operationId: getStablecoin tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Stablecoin' '401': description: Unauthorized content: @@ -7331,7 +7636,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Stablecoin not found content: application/json: schema: @@ -7342,41 +7647,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes: - post: - summary: Create a transfer quote - description: | - Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. - If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. - operationId: agentCreateQuote + /stablecoins/{stablecoinId}/mints: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin mint operation + description: | + Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinMint tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Required idempotency key for retrying this mint request safely. + required: true schema: type: string - example: + maxLength: 255 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/QuoteRequest' + $ref: '#/components/schemas/StablecoinMintRequest' responses: '201': - description: Transfer quote created successfully + description: Mint operation created content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/StablecoinOperation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -7387,141 +7697,88 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '412': - description: Counterparty doesn't support UMA version + '404': + description: Stablecoin or referenced account not found content: application/json: schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + $ref: '#/components/schemas/Error404' + '409': + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}: - parameters: - - name: quoteId - in: path - description: ID of the quote to retrieve - required: true - schema: - type: string - get: - summary: Get agent quote by ID - description: | - Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. - operationId: agentGetQuote - tags: - - Agent Operations - security: - - AgentAuth: [] - responses: - '200': - description: Quote retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '503': + description: Provider temporarily unavailable content: application/json: schema: - $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}/execute: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/burns: parameters: - - name: quoteId + - name: stablecoinId in: path + description: System-generated stablecoin identifier required: true - description: The unique identifier of the quote to execute schema: type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Execute a quote + summary: Create a stablecoin burn operation description: | - Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. - If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - Once executed, the quote cannot be cancelled. - operationId: agentExecuteQuote + Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinBurn tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + description: Required idempotency key for retrying this burn request safely. + required: true schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinBurnRequest' responses: - '200': - description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' + '201': + description: Burn operation created content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinOperation' '400': - description: Bad request - Quote cannot be executed + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Stablecoin or referenced account not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Quote already executed, expired, or in invalid state + description: Conflict content: application/json: schema: @@ -7532,23 +7789,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions: + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/operations: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string get: - summary: List agent's own actions - description: | - Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. - operationId: agentListActions + summary: List stablecoin issuer operations + description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. + operationId: listStablecoinOperations tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: status - in: query - description: Filter by action status - required: false - schema: - $ref: '#/components/schemas/AgentActionStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7560,7 +7823,7 @@ paths: default: 20 - name: cursor in: query - description: Cursor for pagination (returned from previous request) + description: Opaque cursor returned as `nextCursor` from the previous response. required: false schema: type: string @@ -7570,43 +7833,48 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/StablecoinOperationListResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions/{actionId}: + /stablecoin-operations/{stablecoinOperationId}: parameters: - - name: actionId + - name: stablecoinOperationId in: path - description: Unique identifier of the agent action + description: System-generated stablecoin issuer operation identifier required: true schema: type: string get: - summary: Get an agent action - description: | - Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. - operationId: agentGetAction + summary: Get a stablecoin issuer operation + description: Retrieve a single stablecoin issuer operation by its Grid identifier. + operationId: getStablecoinOperation tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinOperation' '401': description: Unauthorized content: @@ -7614,7 +7882,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Action not found + description: Stablecoin operation not found content: application/json: schema: @@ -7625,51 +7893,39 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: + /stablecoin-provider-accounts: post: - summary: Create a transfer-in + summary: Link a stablecoin provider account description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn + Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. + operationId: linkStablecoinProviderAccount tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. + required: true schema: type: string - example: 550e8400-e29b-41d4-a716-446655440000 + maxLength: 255 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 + $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' responses: '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + description: Stablecoin provider account linked content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinProviderAccount' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -7680,119 +7936,37 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found + '409': + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: - post: - summary: Create a transfer-out - description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut + get: + summary: List stablecoin provider account links + description: Retrieve stablecoin provider account links for the authenticated platform. + operationId: listStablecoinProviderAccounts tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header + - name: provider + in: query required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/internal-accounts: - get: - summary: List agent's internal accounts - description: | - Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. - operationId: agentListInternalAccounts - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: type + $ref: '#/components/schemas/StablecoinProvider' + - name: status in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. required: false schema: - $ref: '#/components/schemas/InternalAccountType' + $ref: '#/components/schemas/StablecoinProviderAccountStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7814,7 +7988,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/StablecoinProviderAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -7827,4648 +8007,2114 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts: + /stablecoin-provider-accounts/{stablecoinProviderAccountId}: + parameters: + - name: stablecoinProviderAccountId + in: path + description: System-generated stablecoin provider account link identifier + required: true + schema: + type: string get: - summary: List agent external accounts - description: | - Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentListExternalAccounts + summary: Get a stablecoin provider account link + description: Retrieve a stablecoin provider account link by id for the authenticated platform. + operationId: getStablecoinProviderAccount tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/StablecoinProviderAccount' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin provider account link not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' +webhooks: + agent-action: post: - summary: Add an external account + summary: Agent action pending approval webhook description: | - Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. - operationId: agentCreateExternalAccount + Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: agentActionWebhook tags: - - Agent Operations + - Webhooks security: - - AgentAuth: [] + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' + $ref: '#/components/schemas/AgentActionWebhook' examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet + pendingApproval: + summary: Agent action pending approval value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: AGENT_ACTION.PENDING_APPROVAL + timestamp: '2025-10-03T15:00:00Z' + data: + id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 + agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 + platformCustomerId: user-a1b2c3 + status: PENDING_APPROVAL + type: EXECUTE_QUOTE + quote: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + expiresAt: '2025-10-03T15:00:30Z' + createdAt: '2025-10-03T15:00:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: INR + name: Indian Rupee + symbol: ₹ + decimals: 2 + totalSendingAmount: 50000 + totalReceivingAmount: 4625000 + exchangeRate: 92.5 + feesIncluded: 250 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 + createdAt: '2025-10-03T15:00:00Z' + updatedAt: '2025-10-03T15:00:00Z' responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '200': + description: Webhook received and acknowledged. '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - External account already exists + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get agent external account by ID - description: | - Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentGetExternalAccount + incoming-payment: + post: + summary: Incoming payment webhook and approval mechanism + description: | + Webhook that is called when an incoming payment is received by a customer's UMA address. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Payment Approval Flow + When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: + + 1. The client should check the `counterpartyInformation` against their requirements + 2. To APPROVE the payment synchronously, return a 200 OK response + 3. To REJECT the payment, return a 403 Forbidden response with an Error object + 4. To request more information, return a 422 Unprocessable Entity with specific missing fields + 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. + + The Grid system will proceed or cancel the payment based on your response. + + For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. + operationId: incomingPaymentWebhook tags: - - Agent Operations + - Webhooks security: - - AgentAuth: [] + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhook' + examples: + pendingPayment: + summary: Pending payment example requiring approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: PENDING + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + counterpartyInformation: + FULL_NAME: John Sender + BIRTH_DATE: '1985-06-15' + NATIONALITY: US + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: POSTAL_ADDRESS + mandatory: false + incomingCompletedPayment: + summary: Completed payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + incomingCompletedCryptoPayment: + summary: Completed payment funded from an external crypto wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + type: INCOMING + direction: CREDIT + source: + sourceType: REALTIME_FUNDING + currency: USDC + onChainTransaction: + transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf + network: SOLANA + destination: + destinationType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC deposit from self-custody wallet responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized + description: | + Webhook received successfully. + For PENDING transactions, this indicates approval to proceed with the payment. + If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + $ref: '#/components/schemas/IncomingPaymentWebhookResponse' + '202': + description: | + Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in + cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. + The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent external account - description: | - Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentDeleteExternalAccount - tags: - - Agent Operations - security: - - AgentAuth: [] - responses: - '204': - description: External account deleted successfully - '401': - description: Unauthorized + $ref: '#/components/schemas/Error401' + '403': + description: | + Forbidden - Payment rejected by the client. + Only applicable for PENDING transactions. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error409' + '422': + description: | + Unprocessable Entity - Additional counterparty information required. + Only applicable for PENDING transactions. content: application/json: schema: - $ref: '#/components/schemas/Error500' - /cards: + $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' + outgoing-payment: post: - summary: Issue a card + summary: Outgoing payment status webhook description: | - Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + Webhook that is called when an outgoing payment's status changes. + This endpoint should be implemented by clients of the Grid API. - If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). - operationId: createCard + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. + operationId: outgoingPaymentWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardCreateRequest' + $ref: '#/components/schemas/OutgoingPaymentWebhook' examples: - virtualCard: - summary: Issue a virtual card with one funding source + outgoingCompletedPayment: + summary: Completed outgoing payment value: - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - responses: - '201': - description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '400': - description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivedAmount: + amount: 9706 + currency: + code: EUR + name: Euro + symbol: € + decimals: 2 + exchangeRate: 0.92 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: + counterpartyMultiplier: 1.08 + counterpartyFixedFee: 10 + gridApiMultiplier: 0.925 + gridApiFixedFee: 10 + gridApiVariableFeeRate: 0.003 + gridApiVariableFeeAmount: 30 + outgoingCompletedCryptoPayment: + summary: Completed crypto payout to an external wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000009 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 + onChainTransaction: + transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx + network: SOLANA + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + sentAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC withdrawal to self-custody wallet + paymentInstructions: [] + failedPayment: + summary: Failed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + failureReason: QUOTE_EXECUTION_FAILED + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - get: - summary: List cards + $ref: '#/components/schemas/Error409' + test-webhook: + post: + summary: Test webhook for integration verification description: | - Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. - operationId: listCards + Webhook that is sent once to verify your webhook endpoint is correctly set up. + This is sent when you configure or update your platform settings with a webhook URL. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is purely for testing your endpoint integration and signature verification. + operationId: testWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] - parameters: - - name: cardholderId - in: query - description: Filter by cardholder (customer) id. - required: false - schema: - type: string - - name: accountId - in: query - description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. - required: false - schema: - type: string - - name: platformCardId - in: query - description: Filter by platform-specific card identifier. - required: false - schema: - type: string - - name: state - in: query - description: Filter by card state. - required: false - schema: - $ref: '#/components/schemas/CardState' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestWebhookRequest' + examples: + testWebhook: + summary: Test webhook example + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 + type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CardListResponse' + description: Webhook received successfully. This confirms your webhook endpoint is properly configured. '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - /cards/{id}: - parameters: - - name: id - in: path - description: System-generated unique card identifier - required: true - schema: - type: string - get: - summary: Get a card - description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. - operationId: getCardById + $ref: '#/components/schemas/Error409' + bulk-upload: + post: + summary: Bulk upload status webhook + description: | + Webhook that is called when a bulk customer upload job completes or fails. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. + operationId: bulkUploadWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BulkUploadWebhook' + examples: + completedUpload: + summary: Successful bulk upload completion + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + progress: + total: 5000 + processed: 5000 + successful: 5000 + failed: 0 + errors: [] + failedUpload: + summary: Failed bulk upload + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: FAILED + progress: + total: 5000 + processed: 5000 + successful: 0 + failed: 5000 + errors: + - correlationId: row_1 + error: + code: invalid_csv_format + message: Invalid CSV format + details: + reason: missing_required_column + column: umaAddress responses: '200': - description: Successful operation + description: Webhook received successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - patch: - summary: Update a card + $ref: '#/components/schemas/Error409' + invitation-claimed: + post: + summary: Invitation claimed webhook description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - - - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - - Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): + Webhook that is called when an invitation is claimed by a customer. + This endpoint should be implemented by platform clients of the Grid API. - 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + When a customer claims an invitation, this webhook is triggered to notify the platform that: + 1. The invitation has been successfully claimed + 2. The invitee UMA address is now associated with the invitation + 3. The invitation status has changed from PENDING to CLAIMED - 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + This allows platforms to: + - Track invitation usage and conversion rates + - Trigger onboarding flows for new customers who joined via invitation + - Apply referral bonuses or rewards to the inviter + - Update their UI to reflect the claimed status - Effects: - - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - - `state: ACTIVE`: normal authorization behavior resumes. - - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. - operationId: updateCardById + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: invitationClaimedWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardUpdateRequest' + $ref: '#/components/schemas/InvitationClaimedWebhook' examples: - freeze: - summary: Freeze an active card - value: - state: FROZEN - unfreeze: - summary: Unfreeze a frozen card - value: - state: ACTIVE - updateFundingSources: - summary: Replace the card's bound funding sources - value: - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - freezeAndUpdateSources: - summary: Freeze the card and replace its funding sources in one call - value: - state: FROZEN - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - close: - summary: Permanently close the card + claimedInvitation: + summary: Invitation claimed notification value: - state: CLOSED + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: + code: 019542f5 + createdAt: '2025-09-01T14:30:00Z' + claimedAt: '2025-09-01T15:45:00Z' + inviterUma: $inviter@uma.domain + inviteeUma: $invitee@uma.domain + status: CLAIMED + url: https://uma.me/i/019542f5 responses: '200': - description: Signed retry accepted. Returns the updated card. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '202': - description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/SignedRequestChallenge' + description: Webhook received successfully '400': - description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /cards/{id}/reveal: - parameters: - - name: id - in: path - description: System-generated unique card identifier - required: true - schema: - type: string + customer-update: post: - summary: Reveal card details - description: |- - Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. + summary: Customer status change + description: | + Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. + This endpoint should be implemented by clients of the Grid API. - Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Every reveal is audit-logged with the requesting actor. - operationId: revealCard - tags: - - Cards - security: - - BasicAuth: [] - responses: - '200': - description: Reveal URL minted. - content: - application/json: - schema: - $ref: '#/components/schemas/CardRevealResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /sandbox/cards/{id}/simulate/authorization: - post: - summary: Simulate a card authorization - description: | - Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - - The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - - | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorization + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: customerStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate an authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + $ref: '#/components/schemas/CustomerWebhook' examples: - coffeeAuth: - summary: Approved $12.50 auth at a coffee shop + kycApprovedWebhook: + summary: When an individual customer KYC has been approved value: - amount: 1250 - currency: - code: USD - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - declinedInsufficientFunds: - summary: Declined — insufficient funds (descriptor suffix `002`) + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: San Francisco + state: CA + postalCode: '94105' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When an individual customer KYC has been rejected value: - amount: 50000 - currency: - code: USD - merchant: - descriptor: AMAZON RETAIL US-002 - mcc: '5942' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybApprovedWebhook: + summary: When a business customer KYB has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: CUSTOMER.KYB_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000003 + platformCustomerId: 7a2f9d4e1b8c3f5 + customerType: BUSINESS + region: US + currencies: + - USD + - USDC + umaAddress: $acme.corp@uma.domain.com + kybStatus: APPROVED + address: + line1: 456 Business Ave + city: New York + state: NY + postalCode: '10001' + country: US + businessInfo: + legalName: Acme Corporation + registrationNumber: '12345678' + taxId: 98-7654321 + incorporatedOn: '2018-03-14' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybRejectedWebhook: + summary: When a business customer KYB has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000010 + type: CUSTOMER.KYB_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000004 + platformCustomerId: 3c8e5f2a9d1b7e4 + customerType: BUSINESS + region: US + currencies: + - USD + umaAddress: $globex.inc@uma.domain.com + kybStatus: REJECTED + address: + line1: 789 Corporate Blvd + city: Chicago + state: IL + postalCode: '60601' + country: US + businessInfo: + legalName: Globex Inc + taxId: 47-1234567 + incorporatedOn: '2015-09-01' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/clearing: + $ref: '#/components/schemas/Error409' + internal-account-status: post: - summary: Simulate a card clearing + summary: Internal account status webhook description: | - Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. + Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. + This endpoint should be implemented by clients of the Grid API. - - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). - - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. - - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Production returns `404` on this path. - operationId: sandboxSimulateCardClearing + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Event types + - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. + - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. + operationId: internalAccountStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the clearing applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardClearingRequest' + $ref: '#/components/schemas/InternalAccountStatusWebhook' examples: - tipOnTopClearing: - summary: Clearing larger than auth — exercises post-hoc pull + balanceDecrease: + summary: A transaction just cleared a customer account and the balance has decreased value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 - authorizationExpiry: - summary: Clearing of 0 — exercises authorization expiry + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INTERNAL_ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: ACTIVE + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12500 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + statusUpdated: + summary: The status of an internal account changed (e.g., frozen by Grid) value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 0 + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INTERNAL_ACCOUNT.STATUS_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: FROZEN + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return: + $ref: '#/components/schemas/Error409' + verification-update: post: - summary: Simulate a card return + summary: Verification status change description: | - Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). + Webhook that is called when a customer's KYC/KYB verification status changes. + This endpoint should be implemented by clients of the Grid API. - Production returns `404` on this path. - operationId: sandboxSimulateCardReturn + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: verificationStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the return applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardReturnRequest' + $ref: '#/components/schemas/VerificationWebhook' examples: - fullRefund: - summary: Full refund of a $15.00 settled transaction + approved: + summary: Verification approved value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: VERIFICATION.APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: APPROVED + errors: [] + createdAt: '2025-08-15T14:00:00Z' + resolveErrors: + summary: Verification requires action + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000031 + type: VERIFICATION.RESOLVE_ERRORS + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000011 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + createdAt: '2025-08-15T14:00:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/balance_inquiry: + $ref: '#/components/schemas/Error409' + card-state-change: post: - summary: Simulate a card balance inquiry + summary: Card state change description: | - Simulate a balance-inquiry authorization against a card in the sandbox environment. A balance inquiry is always a `0`-amount authorization, so the request carries no `amount` — only the `merchant`. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - Production returns `404` on this path. - operationId: sandboxSimulateCardBalanceInquiry + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardStateChangeWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a balance inquiry against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardBalanceInquiryRequest' + $ref: '#/components/schemas/CardStateChangeWebhook' examples: - balanceInquiry: - summary: Balance inquiry at a fuel pump + activated: + summary: Card transitioned from PROCESSING to ACTIVE value: - merchant: - descriptor: SHELL OIL 12345678 - mcc: '5541' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/credit_authorization: - post: - summary: Simulate a card credit authorization - description: | - Simulate an inbound credit authorization (a merchant-initiated credit to the card, e.g. a refund pushed as an authorization) in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - - Production returns `404` on this path. - operationId: sandboxSimulateCardCreditAuthorization - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a credit authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - creditAuth: - summary: $25.00 credit authorization + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + processorRef: card_b81c2a4f + issuerRef: lead_card_7a1b9c3d + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:11:00Z' + issuerRejected: + summary: Card rejected by issuer during provisioning value: - amount: 2500 - currency: - code: USD - merchant: - descriptor: ACME MARKETPLACE - mcc: '5942' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000021 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:12:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000011 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: CLOSED + stateReason: ISSUER_REJECTED + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:12:00Z' + frozen: + summary: Card frozen by the platform + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000022 + type: CARD.STATE_CHANGE + timestamp: '2026-05-09T09:00:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: FROZEN + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-09T09:00:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/financial_authorization: + $ref: '#/components/schemas/Error409' + card-funding-source-change: post: - summary: Simulate a card financial authorization + summary: Card funding source change description: | - Simulate a single-message financial authorization (an authorization that clears in the same message, e.g. an ATM withdrawal or other dual-message-exempt flow) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + This endpoint should be implemented by clients of the Grid API. - Production returns `404` on this path. - operationId: sandboxSimulateCardFinancialAuthorization - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a financial authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - financialAuth: - summary: $80.00 single-message ATM withdrawal - value: - amount: 8000 - currency: - code: USD - merchant: - descriptor: ATM WITHDRAWAL 24TH ST - mcc: '6011' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/financial_credit_authorization: - post: - summary: Simulate a card financial credit authorization - description: | - Simulate a single-message financial credit authorization (a credit to the card that clears in the same message, e.g. an ATM deposit or push credit) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + ### Authentication - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Production returns `404` on this path. - operationId: sandboxSimulateCardFinancialCreditAuthorization + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardFundingSourceChangeWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a financial credit authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + $ref: '#/components/schemas/CardFundingSourceChangeWebhook' examples: - financialCreditAuth: - summary: $40.00 single-message push credit + fundingSourcesReplaced: + summary: Funding sources replaced via PATCH /cards/{id} value: - amount: 4000 - currency: - code: USD - merchant: - descriptor: ACME PAYOUTS - mcc: '6012' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: CARD.FUNDING_SOURCE_CHANGE + timestamp: '2026-05-08T14:30:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:30:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/authorization_advice: - post: - summary: Simulate a card authorization advice - description: | - Simulate an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization on a `CardTransaction` in the sandbox environment. The `amount` is the new *total* authorized amount the advice re-sizes to. The parent transaction must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorizationAdvice - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the advice applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationAdviceRequest' - examples: - resizeUp: - summary: Re-size an open auth up to a new total of $20.00 - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 2000 - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/credit_authorization_advice: - post: - summary: Simulate a card credit authorization advice - description: | - Simulate a network-initiated `CREDIT_AUTHORIZATION_ADVICE` against a card in the sandbox environment. Like `simulate/credit_authorization`, it is keyed by the card (PAN) and `merchant` rather than an existing transaction. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - - Production returns `404` on this path. - operationId: sandboxSimulateCardCreditAuthorizationAdvice - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the advice applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - creditAdvice: - summary: $25.00 credit authorization advice - value: - amount: 2500 - currency: - code: USD - merchant: - descriptor: ACME MARKETPLACE - mcc: '5942' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return_reversal: - post: - summary: Simulate a card return reversal - description: | - Simulate a `RETURN_REVERSAL` against an existing `REFUNDED` card transaction in the sandbox environment — reversing a previously settled return. The parent transaction must be in `REFUNDED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - Note: authorization reversal / void has no dedicated card-issuer simulate path (the void simulator emits a distinct `VOID` event), so — like authorization expiry — it is not exposed here. - - Production returns `404` on this path. - operationId: sandboxSimulateCardReturnReversal - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the return reversal applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardTransactionRefRequest' - examples: - returnReversal: - summary: Reverse a settled return on a refunded transaction - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts: - post: - summary: Link a stablecoin provider account - description: | - Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. - operationId: linkStablecoinProviderAccount - tags: - - Stablecoins - security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. - required: true - schema: - type: string - maxLength: 255 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' - responses: - '201': - description: Stablecoin provider account linked - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - get: - summary: List stablecoin provider account links - description: Retrieve stablecoin provider account links for the authenticated platform. - operationId: listStablecoinProviderAccounts - tags: - - Stablecoins - security: - - BasicAuth: [] - parameters: - - name: provider - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProvider' - - name: status - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProviderAccountStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts/{stablecoinProviderAccountId}: - parameters: - - name: stablecoinProviderAccountId - in: path - description: System-generated stablecoin provider account link identifier - required: true - schema: - type: string - get: - summary: Get a stablecoin provider account link - description: Retrieve a stablecoin provider account link by id for the authenticated platform. - operationId: getStablecoinProviderAccount - tags: - - Stablecoins - security: - - BasicAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin provider account link not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' -webhooks: - agent-action: - post: - summary: Agent action pending approval webhook - description: | - Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: agentActionWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionWebhook' - examples: - pendingApproval: - summary: Agent action pending approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: AGENT_ACTION.PENDING_APPROVAL - timestamp: '2025-10-03T15:00:00Z' - data: - id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 - agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 - platformCustomerId: user-a1b2c3 - status: PENDING_APPROVAL - type: EXECUTE_QUOTE - quote: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - expiresAt: '2025-10-03T15:00:30Z' - createdAt: '2025-10-03T15:00:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: INR - name: Indian Rupee - symbol: ₹ - decimals: 2 - totalSendingAmount: 50000 - totalReceivingAmount: 4625000 - exchangeRate: 92.5 - feesIncluded: 250 - platformFeesIncluded: 0 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 - createdAt: '2025-10-03T15:00:00Z' - updatedAt: '2025-10-03T15:00:00Z' - responses: - '200': - description: Webhook received and acknowledged. - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - incoming-payment: - post: - summary: Incoming payment webhook and approval mechanism - description: | - Webhook that is called when an incoming payment is received by a customer's UMA address. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Payment Approval Flow - When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: - - 1. The client should check the `counterpartyInformation` against their requirements - 2. To APPROVE the payment synchronously, return a 200 OK response - 3. To REJECT the payment, return a 403 Forbidden response with an Error object - 4. To request more information, return a 422 Unprocessable Entity with specific missing fields - 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. - - The Grid system will proceed or cancel the payment based on your response. - - For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. - operationId: incomingPaymentWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhook' - examples: - pendingPayment: - summary: Pending payment example requiring approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.PENDING - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: PENDING - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - counterpartyInformation: - FULL_NAME: John Sender - BIRTH_DATE: '1985-06-15' - NATIONALITY: US - reconciliationInstructions: - reference: REF-123456789 - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: POSTAL_ADDRESS - mandatory: false - incomingCompletedPayment: - summary: Completed payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - incomingCompletedCryptoPayment: - summary: Completed payment funded from an external crypto wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - type: INCOMING - direction: CREDIT - source: - sourceType: REALTIME_FUNDING - currency: USDC - onChainTransaction: - transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf - network: SOLANA - destination: - destinationType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC deposit from self-custody wallet - incomingRefundedPayment: - summary: Refunded incoming payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000011 - type: INCOMING_PAYMENT.REFUND_COMPLETED - timestamp: '2025-08-15T14:40:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - failureReason: OFFRAMP_FAILED - refund: - reference: REF-123456789-REFUND - initiatedAt: '2025-08-15T14:35:00Z' - settledAt: '2025-08-15T14:40:00Z' - status: COMPLETED - reason: TRANSACTION_FAILED - responses: - '200': - description: | - Webhook received successfully. - For PENDING transactions, this indicates approval to proceed with the payment. - If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookResponse' - '202': - description: | - Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in - cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. - The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: | - Forbidden - Payment rejected by the client. - Only applicable for PENDING transactions. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '422': - description: | - Unprocessable Entity - Additional counterparty information required. - Only applicable for PENDING transactions. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' - outgoing-payment: - post: - summary: Outgoing payment status webhook - description: | - Webhook that is called when an outgoing payment's status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. - operationId: outgoingPaymentWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OutgoingPaymentWebhook' - examples: - outgoingCompletedPayment: - summary: Completed outgoing payment - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivedAmount: - amount: 9706 - currency: - code: EUR - name: Euro - symbol: € - decimals: 2 - exchangeRate: 0.92 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' - paymentInstructions: [] - rateDetails: - counterpartyMultiplier: 1.08 - counterpartyFixedFee: 10 - gridApiMultiplier: 0.925 - gridApiFixedFee: 10 - gridApiVariableFeeRate: 0.003 - gridApiVariableFeeAmount: 30 - outgoingCompletedCryptoPayment: - summary: Completed crypto payout to an external wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000009 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 - onChainTransaction: - transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx - network: SOLANA - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - sentAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC withdrawal to self-custody wallet - paymentInstructions: [] - failedPayment: - summary: Failed outgoing payment - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - failureReason: QUOTE_EXECUTION_FAILED - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - test-webhook: - post: - summary: Test webhook for integration verification - description: | - Webhook that is sent once to verify your webhook endpoint is correctly set up. - This is sent when you configure or update your platform settings with a webhook URL. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is purely for testing your endpoint integration and signature verification. - operationId: testWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestWebhookRequest' - examples: - testWebhook: - summary: Test webhook example - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000001 - type: TEST - timestamp: '2025-08-15T14:32:00Z' - data: {} - responses: - '200': - description: Webhook received successfully. This confirms your webhook endpoint is properly configured. - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - bulk-upload: - post: - summary: Bulk upload status webhook - description: | - Webhook that is called when a bulk customer upload job completes or fails. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. - operationId: bulkUploadWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BulkUploadWebhook' - examples: - completedUpload: - summary: Successful bulk upload completion - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - progress: - total: 5000 - processed: 5000 - successful: 5000 - failed: 0 - errors: [] - failedUpload: - summary: Failed bulk upload - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: FAILED - progress: - total: 5000 - processed: 5000 - successful: 0 - failed: 5000 - errors: - - correlationId: row_1 - error: - code: invalid_csv_format - message: Invalid CSV format - details: - reason: missing_required_column - column: umaAddress - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - invitation-claimed: - post: - summary: Invitation claimed webhook - description: | - Webhook that is called when an invitation is claimed by a customer. - This endpoint should be implemented by platform clients of the Grid API. - - When a customer claims an invitation, this webhook is triggered to notify the platform that: - 1. The invitation has been successfully claimed - 2. The invitee UMA address is now associated with the invitation - 3. The invitation status has changed from PENDING to CLAIMED - - This allows platforms to: - - Track invitation usage and conversion rates - - Trigger onboarding flows for new customers who joined via invitation - - Apply referral bonuses or rewards to the inviter - - Update their UI to reflect the claimed status - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: invitationClaimedWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/InvitationClaimedWebhook' - examples: - claimedInvitation: - summary: Invitation claimed notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION.CLAIMED - timestamp: '2025-09-01T15:45:00Z' - data: - code: 019542f5 - createdAt: '2025-09-01T14:30:00Z' - claimedAt: '2025-09-01T15:45:00Z' - inviterUma: $inviter@uma.domain - inviteeUma: $invitee@uma.domain - status: CLAIMED - url: https://uma.me/i/019542f5 - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - customer-update: - post: - summary: Customer status change - description: | - Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: customerStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CustomerWebhook' - examples: - kycApprovedWebhook: - summary: When an individual customer KYC has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: CUSTOMER.KYC_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 9f84e0c2a72c4fa - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $john.doe@uma.domain.com - kycStatus: APPROVED - fullName: John Michael Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - line2: Apt 4B - city: Seattle - state: WA - postalCode: '98101' - country: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-07-21T17:32:28Z' - isDeleted: false - kycRejectedWebhook: - summary: When an individual customer KYC has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: CUSTOMER.KYC_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000002 - platformCustomerId: 4b7c1e9d3f5a8e2 - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $jane.smith@uma.domain.com - kycStatus: REJECTED - fullName: Jane Smith - birthDate: '1988-03-22' - nationality: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybApprovedWebhook: - summary: When a business customer KYB has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: CUSTOMER.KYB_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000003 - platformCustomerId: 7a2f9d4e1b8c3f5 - customerType: BUSINESS - region: US - currencies: - - USD - - USDC - umaAddress: $acme.corp@uma.domain.com - kybStatus: APPROVED - address: - line1: 456 Business Ave - city: Seattle - state: WA - postalCode: '98101' - country: US - businessInfo: - legalName: Acme Corporation - registrationNumber: '12345678' - taxId: 98-7654321 - incorporatedOn: '2018-03-14' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybRejectedWebhook: - summary: When a business customer KYB has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000010 - type: CUSTOMER.KYB_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000004 - platformCustomerId: 3c8e5f2a9d1b7e4 - customerType: BUSINESS - region: US - currencies: - - USD - umaAddress: $globex.inc@uma.domain.com - kybStatus: REJECTED - address: - line1: 789 Corporate Blvd - city: Chicago - state: IL - postalCode: '60601' - country: US - businessInfo: - legalName: Globex Inc - taxId: 47-1234567 - incorporatedOn: '2015-09-01' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - internal-account-status: - post: - summary: Internal account status webhook - description: | - Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Event types - - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. - - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. - operationId: internalAccountStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountStatusWebhook' - examples: - balanceDecrease: - summary: A transaction just cleared a customer account and the balance has decreased - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INTERNAL_ACCOUNT.BALANCE_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: ACTIVE - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12500 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - statusUpdated: - summary: The status of an internal account changed (e.g., frozen by Grid) - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INTERNAL_ACCOUNT.STATUS_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: FROZEN - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - verification-update: - post: - summary: Verification status change - description: | - Webhook that is called when a customer's KYC/KYB verification status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: verificationStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/VerificationWebhook' - examples: - approved: - summary: Verification approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: VERIFICATION.APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: APPROVED - errors: [] - createdAt: '2025-08-15T14:00:00Z' - resolveErrors: - summary: Verification requires action - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000031 - type: VERIFICATION.RESOLVE_ERRORS - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000011 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - createdAt: '2025-08-15T14:00:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-state-change: - post: - summary: Card state change - description: | - Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardStateChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardStateChangeWebhook' - examples: - activated: - summary: Card transitioned from PROCESSING to ACTIVE - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:11:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - processorRef: card_b81c2a4f - issuerRef: lead_card_7a1b9c3d - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:11:00Z' - issuerRejected: - summary: Card rejected by issuer during provisioning - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000021 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:12:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000011 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: CLOSED - stateReason: ISSUER_REJECTED - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:12:00Z' - frozen: - summary: Card frozen by the platform - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000022 - type: CARD.STATE_CHANGE - timestamp: '2026-05-09T09:00:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: FROZEN - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-09T09:00:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-funding-source-change: - post: - summary: Card funding source change - description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' - examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-transaction: - post: - summary: Card transaction - description: | - Webhook that is called on every state transition of a card `CardTransaction`. Fires when an authorization is approved (`CARD_TRANSACTION.AUTHORIZED`), as clearings settle against it (`CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`), when settled funds are returned (`CARD_TRANSACTION.REFUNDED`), and when a pull or confirmation fails (`CARD_TRANSACTION.EXCEPTION`). The payload carries the full `CardTransaction` resource. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardTransactionWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardTransactionWebhook' - examples: - authorized: - summary: Authorization approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000040 - type: CARD_TRANSACTION.AUTHORIZED - timestamp: '2026-05-09T10:00:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: AUTHORIZED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 1 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 0 - totalAmount: 0 - authorizedAt: '2026-05-09T10:00:00Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-09T10:00:00Z' - settled: - summary: All clearings posted - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000041 - type: CARD_TRANSACTION.SETTLED - timestamp: '2026-05-09T15:42:11Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: SETTLED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 0 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 1 - totalAmount: 12550 - authorizedAt: '2026-05-09T10:00:00Z' - lastEventAt: '2026-05-09T15:42:11Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-09T15:42:11Z' - refunded: - summary: Settled funds returned by the merchant - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000042 - type: CARD_TRANSACTION.REFUNDED - timestamp: '2026-05-10T09:15:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: REFUNDED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - refundedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 0 - refundSummary: - count: 1 - totalAmount: 12550 - settlementSummary: - count: 1 - totalAmount: 12550 - authorizedAt: '2026-05-09T10:00:00Z' - lastEventAt: '2026-05-10T09:15:00Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-10T09:15:00Z' - exception: - summary: Settled to the network but the funding-source pull failed - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000043 - type: CARD_TRANSACTION.EXCEPTION - timestamp: '2026-05-09T16:05:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000101 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_c92d3b5a - status: EXCEPTION - direction: DEBIT - merchant: - descriptor: UBER EATS - mcc: '5812' - country: US - authorizedAmount: - amount: 4200 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 4200 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 0 - pendingCount: 0 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 1 - totalAmount: 4200 - authorizedAt: '2026-05-09T15:30:00Z' - lastEventAt: '2026-05-09T16:05:00Z' - createdAt: '2026-05-09T15:30:00Z' - updatedAt: '2026-05-09T16:05:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' -components: - securitySchemes: - BasicAuth: - type: http - scheme: basic - description: API token authentication using format `:` - AgentAuth: - type: http - scheme: bearer - description: 'Bearer token authentication for agent-scoped endpoints. The token is the `accessToken` returned when redeeming a device code via `POST /agents/device-codes/{code}/redeem`. Agent credentials are user-scoped: all requests are automatically bound to the agent''s associated customer and subject to the agent''s policy.' - WebhookSignature: - type: apiKey - in: header - name: X-Grid-Signature - description: | - Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - schemas: - CustomerInfoFieldName: - type: string - enum: - - FULL_NAME - - BIRTH_DATE - - NATIONALITY - - PHONE_NUMBER - - EMAIL - - POSTAL_ADDRESS - - TAX_ID - - REGISTRATION_NUMBER - - USER_TYPE - - COUNTRY_OF_RESIDENCE - - ACCOUNT_IDENTIFIER - - FI_LEGAL_ENTITY_NAME - - FI_ADDRESS - - PURPOSE_OF_PAYMENT - - ULTIMATE_INSTITUTION_COUNTRY - - IDENTIFIER - - BUSINESS_TYPE - - COMPANY_LEGAL_NAME - - ID_TYPE - - ID_NUMBER - description: Name of a type of field containing info about a platform's customer or counterparty customer. - example: FULL_NAME - CounterpartyFieldDefinition: - type: object - properties: - name: - $ref: '#/components/schemas/CustomerInfoFieldName' - mandatory: - type: boolean - description: Whether the field is mandatory - example: true - required: - - name - - mandatory - TransactionType: - type: string - enum: - - INCOMING - - OUTGOING - description: Type of transaction (incoming payment or outgoing payment) - PlatformCurrencyConfig: - type: object - properties: - currencyCode: - type: string - description: Three-letter currency code (ISO 4217) - example: USD - minAmount: - type: integer - format: int64 - description: Minimum amount that can be sent in the smallest unit of this currency - minimum: 0 - example: 100 - maxAmount: - type: integer - format: int64 - description: Maximum amount that can be sent in the smallest unit of this currency - minimum: 0 - example: 1000000 - requiredCounterpartyFields: - type: array - items: - $ref: '#/components/schemas/CounterpartyFieldDefinition' - description: List of fields which the platform requires from the counterparty institutions about counterparty customers. Platforms can set mandatory to false if the platform does not require the field, but would like to have it available. Some fields may be required by the underlying UMA provider. - example: - - name: FULL_NAME - mandatory: true - - name: BIRTH_DATE - mandatory: true - - name: NATIONALITY - mandatory: true - providerRequiredCustomerFields: - type: array - items: - $ref: '#/components/schemas/CustomerInfoFieldName' - description: List of customer info field names that are required by the underlying UMA provider when creating a customer for this currency. These fields must be supplied when creating or updating a customer if this currency is intended to be used by that customer. If no fields are required, this field is omitted. - readOnly: true - example: - - NATIONALITY - - BIRTH_DATE - providerRequiredCounterpartyCustomerFields: - type: array - items: - $ref: '#/components/schemas/CustomerInfoFieldName' - description: List of fields that are required by the underlying UMA provider for this currency. If the counterparty does not provide these fields, quote requests will fail. - readOnly: true - example: - - FULL_NAME - - COUNTRY_OF_RESIDENCE - enabledTransactionTypes: - type: array - items: - $ref: '#/components/schemas/TransactionType' - description: List of transaction types that are enabled for this currency. - example: - - OUTGOING - - INCOMING - required: - - currencyCode - - minAmount - - maxAmount - - requiredCounterpartyFields - - enabledTransactionTypes - EmbeddedWalletConfig: - type: object - description: | - Per-platform embedded-wallet configuration. Controls branding and OTP - behavior for the email sent when a customer authenticates with an - EMAIL_OTP credential. Fields omitted from a request are left unchanged. - properties: - appName: - type: string - maxLength: 255 - description: App name displayed in the default OTP email template. - example: Acme Wallet - otpLength: - type: integer - minimum: 4 - maximum: 12 - description: | - Number of digits / characters in the OTP code. Defaults to 6 when - not set. - example: 6 - alphanumeric: - type: boolean - description: | - If true, OTP includes letters in addition to digits. Defaults to - numeric-only when not set. - example: false - expirationSeconds: - type: integer - minimum: 1 - maximum: 86400 - description: | - OTP validity window in seconds. Defaults to 300 when not set. - example: 300 - sendFromEmailAddress: - type: string - format: email - maxLength: 255 - description: Custom sender email address for OTP emails. - example: noreply@acme.com - sendFromEmailSenderName: - type: string - maxLength: 255 - description: | - Custom sender display name. Defaults to "Notifications" when not set. - example: Acme Notifications - replyToEmailAddress: - type: string - format: email - maxLength: 255 - description: Custom reply-to email address for OTP emails. - example: support@acme.com - logoUrl: - type: string - format: uri - maxLength: 512 - description: URL to a PNG logo for the OTP email. Resized to 340x124px. - example: https://acme.com/logo.png - CardTokenization2FAEmailConfig: - type: object - description: Email branding and sender settings for card-tokenization authentication messages. Invalid or unverified sender identities can cause delivery to fail. - properties: - fromAddress: - type: string - format: email - maxLength: 255 - description: Sender address for card-tokenization authentication emails. - example: cards@acme.com - fromName: - type: string - maxLength: 255 - description: Sender display name. - example: Acme Cards - replyToAddress: - type: string - format: email - maxLength: 255 - description: Reply-to address for card-tokenization authentication emails. - example: support@acme.com - subject: - type: string - maxLength: 255 - description: Subject for the authentication email. - example: Your Acme card verification code - bodyText: - type: string - maxLength: 2000 - description: | - Plain-text message content. Lightspark inserts the authentication code into - a controlled text and HTML template; arbitrary HTML and template variables - are not supported. - example: Use this code to finish adding your Acme card to your digital wallet. - CardTokenization2FASmsConfig: - type: object - description: SMS settings for card-tokenization authentication messages delivered through a Lightspark-managed Twilio sender. - properties: - templateSid: - type: string - maxLength: 64 - description: | - Twilio Verify template SID to use for this platform. An invalid or unavailable - template can cause delivery to fail. - example: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - bodyText: - type: string - maxLength: 480 - description: | - Plain-text fallback message used when Twilio Verify is unavailable for the - authentication code. Lightspark appends the code to this text. - example: Use this code to finish adding your Acme card to your digital wallet. - CardTokenization2FAConfig: - type: object - description: Per-platform branding for authentication codes sent when a cardholder adds a Grid-issued card to a digital wallet. Updates apply to subsequent delivery attempts. Invalid sender or provider configuration can cause delivery to fail. - properties: - displayName: - type: string - maxLength: 255 - description: Platform name displayed in authentication messages. - example: Acme - logoUrl: - type: string - format: uri - pattern: ^https:// - maxLength: 512 - description: HTTPS URL of the logo displayed in email messages. - example: https://acme.com/card-email-logo.png - email: - $ref: '#/components/schemas/CardTokenization2FAEmailConfig' - sms: - $ref: '#/components/schemas/CardTokenization2FASmsConfig' - FeeType: - type: string - enum: - - CROSS_CURRENCY_TRANSACTION - description: | - The kind of activity this fee applies to. - - - `CROSS_CURRENCY_TRANSACTION` — fee charged on a cross-currency Grid transaction - (source currency differs from destination currency). - example: CROSS_CURRENCY_TRANSACTION - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 - CurrencyAmount: - type: object - required: - - amount - - currency - properties: - amount: - type: integer - format: int64 - description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) - example: 12550 - currency: - $ref: '#/components/schemas/Currency' - FeeConfig: - type: object - description: A platform-configured fee collected by Grid and settled to the platform internal account. There can be at most one fee config for a given fee type and source currency pair. The fee will apply to all transactions of the fee type that originate in the source currency. - properties: - feeType: - $ref: '#/components/schemas/FeeType' - sourceCurrency: - type: string - description: Currency code of the sending side this fee applies to. Only `USD` is accepted today; other currencies return a `NOT_IMPLEMENTED` error. - example: USD - variableFeeBps: - type: integer - minimum: 0 - maximum: 10000 - description: Variable fee in basis points (1 bps = 0.01%) to apply to a transaction's source-currency amount. - example: 30 - fixedFee: - $ref: '#/components/schemas/CurrencyAmount' - description: Fixed fee charged per transaction, in the smallest unit of the source currency. The fixed fee currency must match the fee config's `sourceCurrency`. - required: - - feeType - - sourceCurrency - - variableFeeBps - - fixedFee - PlatformConfig: - type: object - properties: - id: - type: string - description: System-generated unique identifier - readOnly: true - example: PlatformConfig:019542f5-b3e7-1d02-0000-000000000003 - umaDomain: - type: string - description: UMA domain for this platform - example: platform.uma.domain - proxyUmaSubdomain: - type: string - description: The subdomain that incoming requests will be proxied to - example: platform - webhookEndpoint: - type: string - description: URL where webhook notifications will be sent - example: https://api.mycompany.com/webhooks/uma - supportedCurrencies: - type: array - items: - $ref: '#/components/schemas/PlatformCurrencyConfig' - description: | - List of currencies supported by the platform. This is what the platform's - customers are able to hold, send, and receive. - isRegulatedFinancialInstitution: - type: boolean - description: | - Whether the platform is a regulated financial institution. This is used to - determine if the platform's customers must be KYC/KYB'd by Lightspark via - the KYC link flow. This can only be set by Lightspark during platform - creation. - example: false - embeddedWalletConfig: - $ref: '#/components/schemas/EmbeddedWalletConfig' - description: | - Embedded-wallet branding and OTP settings for this platform. Present - only when the platform has configured embedded-wallet support; - omitted otherwise. - cardTokenization2faConfig: - $ref: '#/components/schemas/CardTokenization2FAConfig' - description: | - Branding and sender configuration for card-tokenization authentication - messages. This configuration is independent of embedded-wallet support. - feeConfigs: - type: array - items: - $ref: '#/components/schemas/FeeConfig' - description: | - Platform-collected fees that should be added on top of - Grid-collected fees. Contains every currently-active fee config - for the platform. - createdAt: - type: string - format: date-time - description: Creation timestamp - readOnly: true - example: '2025-06-15T12:30:45Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - readOnly: true - example: '2025-06-15T12:30:45Z' - Error401: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 401 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | UNAUTHORIZED | Issue with API credentials | - | INVALID_SIGNATURE | Signature header is invalid | - | WALLET_SIGNATURE_MISSING | The `Grid-Wallet-Signature` header is required for this Embedded Wallet action but was not supplied | - | WALLET_SIGNATURE_MALFORMED | The `Grid-Wallet-Signature` header could not be parsed (bad encoding, structure, or fields) | - | WALLET_SIGNATURE_BODY_MISMATCH | The `Grid-Wallet-Signature` was computed over a different request body than the one received | - | WALLET_SIGNATURE_INVALID | The `Grid-Wallet-Signature` failed cryptographic verification against the registered credential | - | REQUEST_ID_MISSING | The `Request-Id` header is required on the signed retry but was not supplied (paired with `Grid-Wallet-Signature`) | - enum: - - UNAUTHORIZED - - INVALID_SIGNATURE - - WALLET_SIGNATURE_MISSING - - WALLET_SIGNATURE_MALFORMED - - WALLET_SIGNATURE_BODY_MISMATCH - - WALLET_SIGNATURE_INVALID - - REQUEST_ID_MISSING - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error500: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 500 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | GRID_SWITCH_ERROR | Grid switch error | - | INTERNAL_ERROR | Internal server or UMA error | - enum: - - GRID_SWITCH_ERROR - - INTERNAL_ERROR - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - PlatformConfigUpdateRequest: - type: object - properties: - umaDomain: - type: string - example: mycompany.com - webhookEndpoint: - type: string - example: https://api.mycompany.com/webhooks/uma - supportedCurrencies: - type: array - items: - $ref: '#/components/schemas/PlatformCurrencyConfig' - embeddedWalletConfig: - $ref: '#/components/schemas/EmbeddedWalletConfig' - description: | - Update or create the embedded-wallet configuration for this platform. - Fields omitted from the nested object are left unchanged. Omit this - field at the top level to leave the embedded-wallet configuration - unchanged entirely. - cardTokenization2faConfig: - $ref: '#/components/schemas/CardTokenization2FAConfig' - description: | - Update card-tokenization authentication branding and delivery settings. - Fields omitted from the nested object are left unchanged. Changes apply - to subsequent delivery attempts. - feeConfigs: - type: array - items: - $ref: '#/components/schemas/FeeConfig' - description: | - Merge-by-key upsert of platform fee configs, keyed by - `(feeType, sourceCurrency)`. Setting variable and fixed fees to 0 for - an existing fee config deactivates it. Only `sourceCurrency: USD` is - accepted today. Omit this field to leave fee configs unchanged. - FieldConstraint: - type: object - description: Machine-readable validator hint accompanying a 400 `INVALID_INPUT` error. Consumers use it to drive form UI (input types, dropdowns, masking, length limits) and to pre-validate the field client-side before re-submitting. Fields are additive. - properties: - format: - type: string - description: Named format the value must satisfy — HTML5 input type names (`email`, `tel`, `url`, `date`, ...) or semantic slugs (`iso3166-1-alpha-2`, `bcp47-language-tag`, `us-ssn`, `e.164`). - example: email - pattern: - type: string - description: Regular expression the value must match (JavaScript-flavor). - example: ^\d{5}(-\d{4})?$ - enum: - type: array - items: - type: string - description: Allowed values when the field is drawn from a fixed set. - example: - - SSN - - ITIN - - NON_US_TAX_ID - minLength: - type: integer - description: Minimum length in characters. - example: 1 - maxLength: - type: integer - description: Maximum length in characters. - example: 500 - FieldError: - type: object - required: - - field - description: One field-level validation failure. Field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`) emit an array of these under `details.errors` so platforms can render form-field-level UX for every failure in a single round-trip. - properties: - field: - type: string - description: Dot-notation path to the offending field. - example: taxIdentifier - constraint: - $ref: '#/components/schemas/FieldConstraint' - message: - type: string - description: Human-readable explanation of what's wrong with this field. - example: Value is not one of the allowed enum members. - Error400: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 400 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | INVALID_INPUT | Invalid input provided | - | MISSING_MANDATORY_USER_INFO | Required customer information is missing | - | INVITATION_ALREADY_CLAIMED | Invitation has already been claimed | - | INVITATIONS_NOT_CONFIGURED | Invitations are not configured | - | INVALID_UMA_ADDRESS | UMA address format is invalid | - | INVITATION_CANCELLED | Invitation has been cancelled | - | QUOTE_REQUEST_FAILED | An issue occurred during the quote process; this is retryable | - | INVALID_PAYREQ_RESPONSE | Counterparty Payreq response was invalid | - | INVALID_RECEIVER | Receiver is invalid | - | PARSE_PAYREQ_RESPONSE_ERROR | Error parsing receiver PayReq response | - | CERT_CHAIN_INVALID | Counterparty certificate chain is invalid | - | CERT_CHAIN_EXPIRED | Counterparty certificate chain has expired | - | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | - | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | - | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range | - | INVALID_CURRENCY | Currency is invalid | - | INVALID_TIMESTAMP | Timestamp is invalid | - | INVALID_NONCE | Nonce is invalid | - | INVALID_REQUEST_FORMAT | Request format is invalid | - | INVALID_BANK_ACCOUNT | Bank account is invalid | - | SELF_PAYMENT | Self payment not allowed | - | LOOKUP_REQUEST_FAILED | Lookup request failed | - | PARSE_LNURLP_RESPONSE_ERROR | Error parsing LNURLP response | - | INVALID_AMOUNT | Amount is invalid | - | WEBHOOK_ENDPOINT_NOT_SET | Webhook endpoint is not set | - | WEBHOOK_DELIVERY_ERROR | Webhook delivery error | - | LOW_QUALITY | Document quality too low to process | - | DATA_MISMATCH | Document details don't match provided information | - | EXPIRED | Document has expired | - | SUSPECTED_FRAUD | Document suspected of being forged or edited | - | UNSUITABLE_DOCUMENT | Document type is not accepted or not supported | - | INCOMPLETE | Document is missing pages or sides | - | EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS | An EMAIL_OTP credential is already registered on the target internal account; only one email OTP credential is supported per internal account at this time | - | SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time | - | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account | - | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | - | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | - | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - enum: - - INVALID_INPUT - - MISSING_MANDATORY_USER_INFO - - INVITATION_ALREADY_CLAIMED - - INVITATIONS_NOT_CONFIGURED - - INVALID_UMA_ADDRESS - - INVITATION_CANCELLED - - QUOTE_REQUEST_FAILED - - INVALID_PAYREQ_RESPONSE - - INVALID_RECEIVER - - PARSE_PAYREQ_RESPONSE_ERROR - - CERT_CHAIN_INVALID - - CERT_CHAIN_EXPIRED - - INVALID_PUBKEY_FORMAT - - MISSING_REQUIRED_UMA_PARAMETERS - - SENDER_NOT_ACCEPTED - - AMOUNT_OUT_OF_RANGE - - INVALID_CURRENCY - - INVALID_TIMESTAMP - - INVALID_NONCE - - INVALID_REQUEST_FORMAT - - INVALID_BANK_ACCOUNT - - SELF_PAYMENT - - LOOKUP_REQUEST_FAILED - - PARSE_LNURLP_RESPONSE_ERROR - - INVALID_AMOUNT - - WEBHOOK_ENDPOINT_NOT_SET - - WEBHOOK_DELIVERY_ERROR - - LOW_QUALITY - - DATA_MISMATCH - - EXPIRED - - SUSPECTED_FRAUD - - UNSUITABLE_DOCUMENT - - INCOMPLETE - - EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS - - SMS_OTP_CREDENTIAL_ALREADY_EXISTS - - PASSKEY_CREDENTIAL_ALREADY_EXISTS - - STABLECOIN_PROVIDER_ACCOUNT_INVALID - - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - message: - type: string - description: Error message - details: - type: object - description: Additional error details. Shape varies by `code`. For field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`), `details.errors[]` enumerates every invalid field so platforms can render form-field-level UX for the entire request in a single round-trip. - properties: - errors: - type: array - description: One entry per invalid field. Present on field-validation errors from submit endpoints. - items: - $ref: '#/components/schemas/FieldError' - additionalProperties: true - Error501: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 501 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory payee data key | - | NOT_IMPLEMENTED | Feature not implemented | - enum: - - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY - - NOT_IMPLEMENTED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - PaymentRail: - type: string - enum: - - ACH - - ACH_COLOMBIA - - BANK_TRANSFER - - BRE_B - - CIPS - - FAST - - FASTER_PAYMENTS - - FEDNOW - - INSTAPAY - - MOBILE_MONEY - - NEFT - - PAYNOW - - PESONET - - PIX - - RTGS - - RTP - - SEPA - - SEPA_INSTANT - - SPEI - - SWIFT - - UNIONPAY - - UPI - - WIRE - description: The payment rail used for the transfer. Payment rails represent the underlying payment network or system used to move funds between accounts. - example: ACH - ExchangeRateFees: - type: object - description: Fees associated with an exchange rate - properties: - fixed: - type: integer - format: int64 - description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) - minimum: 0 - example: 100 - total: - type: integer - format: int64 - description: Total fees in the smallest unit of the sending currency (e.g., cents for USD). This value may change depending on the sending amount used; if no sending amount is specified, it falls back to the default. - minimum: 0 - example: 100 - ExchangeRate: - type: object - description: Exchange rate information for a currency corridor - required: - - sourceCurrency - - destinationCurrency - - destinationPaymentRail - - minSendingAmount - - maxSendingAmount - - sendingAmount - - receivingAmount - - exchangeRate - - fees - - updatedAt - properties: - sourceCurrency: - $ref: '#/components/schemas/Currency' - sendingAmount: - type: integer - format: int64 - description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. - minimum: 0 - example: 10000 - minSendingAmount: - type: integer - format: int64 - description: The minimum supported sending amount in the smallest unit of the source currency. - minimum: 0 - example: 100 - maxSendingAmount: - type: integer - format: int64 - description: The maximum supported sending amount in the smallest unit of the source currency. - minimum: 0 - example: 10000000 - destinationCurrency: - $ref: '#/components/schemas/Currency' - destinationPaymentRail: - allOf: - - $ref: '#/components/schemas/PaymentRail' - - description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) - example: UPI - receivingAmount: - type: integer - format: int64 - description: The receiving amount in the smallest unit of the destination currency - minimum: 0 - example: 1650000 - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 0.012121 - fees: - $ref: '#/components/schemas/ExchangeRateFees' - updatedAt: - type: string - format: date-time - description: Timestamp when this exchange rate was last refreshed - example: '2025-02-05T12:00:00Z' - ExchangeRateListResponse: - type: object - required: - - data - properties: - data: - type: array - description: List of exchange rates matching the filter criteria - items: - $ref: '#/components/schemas/ExchangeRate' - DiscoveryListResponse: - type: object - properties: - data: - type: array - description: List of payment rails matching the filter criteria - items: - type: object - required: - - bankName - - displayName - - country - - currency - properties: - bankName: - type: string - description: | - Canonical name for this payment rail. Pass this value as `bankName` when creating an external account. - displayName: - type: string - description: Human-friendly display name for this payment rail. - country: - type: string - description: ISO 3166-1 alpha-2 country code. - currency: - type: string - description: ISO 4217 currency code. - CustomerType: - type: string - enum: - - INDIVIDUAL - - BUSINESS - description: Whether the customer is an individual or a business entity - example: INDIVIDUAL - ContactVerificationStatus: - type: string - description: Status of an individual contact-verification channel (email or phone). `PENDING` means verification is required but not yet completed; `VERIFIED` means the channel has been confirmed. - enum: - - PENDING - - VERIFIED - example: VERIFIED - ContactVerification: - type: object - description: |- - Email and/or phone verification state for the customer. This object is **only present when the customer's regulatory jurisdiction requires contact verification** (e.g. EU customers). For customers who have no such requirement, this object is omitted entirely — no action is needed. - - Each channel is reported independently: only the channels the customer's provider actually requires are present. A provider may require both email and phone, just one of them, or — when the object is absent — neither. Every channel that **is** present must reach `VERIFIED` before the customer can begin KYC. Drive each present channel with `POST /customers/{customerId}/verify-email` and/or `POST /customers/{customerId}/verify-phone` (and their `/confirm` sub-routes). - properties: - email: - $ref: '#/components/schemas/ContactVerificationStatus' - description: Verification status of the customer's email address. Present only when the provider requires email verification. - phone: - $ref: '#/components/schemas/ContactVerificationStatus' - description: Verification status of the customer's phone number. Present only when the provider requires phone verification. - Customer: - type: object - required: - - umaAddress - - platformCustomerId - - customerType - properties: - id: - type: string - description: System-generated unique identifier - readOnly: true - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: - type: string - description: Platform-specific customer identifier - example: 9f84e0c2a72c4fa - customerType: - $ref: '#/components/schemas/CustomerType' - region: - type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity and regulatory jurisdiction. - example: US - currencies: - type: array - items: - type: string - description: List of currency codes enabled for this customer. - example: - - USD - - USDC - email: - type: string - format: email - description: Email address for the customer. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. - example: '+14155551234' - umaAddress: - type: string - description: Full UMA address (always present in responses, even if system-generated). This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - createdAt: - type: string - format: date-time - description: Creation timestamp - readOnly: true - example: '2025-07-21T17:32:28Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - readOnly: true - example: '2025-07-21T17:32:28Z' - isDeleted: - type: boolean - description: Whether the customer is marked as deleted - example: false - readOnly: true - contactVerification: - allOf: - - $ref: '#/components/schemas/ContactVerification' - readOnly: true - description: Email and phone verification state. **Only present when the customer's payment provider requires it** (e.g. EU customers); omitted otherwise. - KycStatus: +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + description: API token authentication using format `:` + AgentAuth: + type: http + scheme: bearer + description: 'Bearer token authentication for agent-scoped endpoints. The token is the `accessToken` returned when redeeming a device code via `POST /agents/device-codes/{code}/redeem`. Agent credentials are user-scoped: all requests are automatically bound to the agent''s associated customer and subject to the agent''s policy.' + WebhookSignature: + type: apiKey + in: header + name: X-Grid-Signature + description: | + Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + schemas: + CustomerInfoFieldName: type: string enum: - - UNVERIFIED - - PENDING - - APPROVED - - REJECTED - - HOLD - description: The current KYC status of a customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. - example: APPROVED - Address: + - FULL_NAME + - BIRTH_DATE + - NATIONALITY + - PHONE_NUMBER + - EMAIL + - POSTAL_ADDRESS + - TAX_ID + - REGISTRATION_NUMBER + - USER_TYPE + - COUNTRY_OF_RESIDENCE + - ACCOUNT_IDENTIFIER + - FI_LEGAL_ENTITY_NAME + - FI_ADDRESS + - PURPOSE_OF_PAYMENT + - ULTIMATE_INSTITUTION_COUNTRY + - IDENTIFIER + - BUSINESS_TYPE + - COMPANY_LEGAL_NAME + - ID_TYPE + - ID_NUMBER + description: Name of a type of field containing info about a platform's customer or counterparty customer. + example: FULL_NAME + CounterpartyFieldDefinition: type: object - required: - - line1 - - postalCode - - country properties: - line1: - type: string - description: Street address line 1 - example: 123 Main Street - line2: - type: string - description: Street address line 2 - example: Apt 4B - city: - type: string - description: City - example: San Francisco - state: - type: string - description: State/Province/Region - example: CA - postalCode: - type: string - description: Postal/ZIP code - example: '94105' - country: - type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - IdentificationType: - type: string - enum: - - SSN - - ITIN - - EIN - - NON_US_TAX_ID - description: Type of tax identification - example: SSN - IndividualSourceOfFundsCategory: - type: string - enum: - - SALARY - - SELF_EMPLOYMENT_INCOME - - INVESTMENT_INCOME - - PENSION - - RENTAL_INCOME - - GIFT - - INHERITANCE - - LOAN - - SAVINGS - - SALE_OF_ASSETS - - OTHER - description: A structured source-of-funds category for an individual customer (FLOW of funds into this account). Distinct from `SourceOfFundsCategory`, which covers business customers. - example: SALARY - SourceOfWealthCategory: - type: string - enum: - - SALARY - - BUSINESS_INCOME - - INVESTMENTS - - INHERITANCE - - PROPERTY_SALE - - GIFT - - RETIREMENT - - SAVINGS - - OTHER - description: Origin of the customer's accumulated wealth (STOCK). Distinct from `SourceOfFundsCategory` which describes the FLOW of funds for this specific account. - example: SALARY - PurposeOfAccount: - type: string - enum: - - CONTRACTOR_PAYOUTS - - CREATOR_PAYOUTS - - EMPLOYEE_PAYOUTS - - MARKETPLACE_SELLER_PAYOUTS - - SUPPLIER_PAYMENTS - - CROSS_BORDER_B2B - - AR_AUTOMATION - - AP_AUTOMATION - - EMBEDDED_PAYMENTS - - PLATFORM_FEE_COLLECTION - - P2P_TRANSFERS - - CHARITABLE_DONATIONS - - OTHER - description: The intended purpose for using the Grid account - example: CONTRACTOR_PAYOUTS - ExpectedMonthlyTransactionCount: - type: string - enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - ExpectedMonthlyTransactionVolume: - type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - AnnualIncomeRange: - type: string - enum: - - UNDER_50K - - RANGE_50K_100K - - RANGE_100K_250K - - RANGE_250K_1M - - OVER_1M - description: Bucketed annual income (USD equivalent). Used for enhanced due diligence on higher-risk profiles. - example: RANGE_100K_250K - NetWorthRange: - type: string - enum: - - UNDER_100K - - RANGE_100K_500K - - RANGE_500K_1M - - RANGE_1M_5M - - RANGE_5M_25M - - OVER_25M - description: Bucketed total net worth (USD equivalent). Used for enhanced due diligence on higher-risk profiles. - example: RANGE_500K_1M - PepStatus: + name: + $ref: '#/components/schemas/CustomerInfoFieldName' + mandatory: + type: boolean + description: Whether the field is mandatory + example: true + required: + - name + - mandatory + TransactionType: type: string enum: - - NONE - - DOMESTIC - - FOREIGN - - HIO - - FAMILY_OR_ASSOCIATE - description: Political exposure declaration (Politically Exposed Person status). `HIO` = head of an international organization. `FAMILY_OR_ASSOCIATE` covers close family members and known close associates of a PEP. - example: NONE - CustomerEdd: + - INCOMING + - OUTGOING + description: Type of transaction (incoming payment or outgoing payment) + PlatformCurrencyConfig: type: object - description: Enhanced-due-diligence (EDD) fields available as optional patchable attributes on an individual customer. Referenced via `allOf` from `IndividualCustomerFields`, so these appear as top-level optional fields on the customer resource itself; there is no separate EDD resource. The specific set required for a given customer is driven by the KYC provider's per-jurisdiction / per-flow / per-volume-tier rules (surfaced through `MISSING_FIELD` errors on `POST /verifications`). properties: - sourceOfFundsCategories: + currencyCode: + type: string + description: Three-letter currency code (ISO 4217) + example: USD + minAmount: + type: integer + format: int64 + description: Minimum amount that can be sent in the smallest unit of this currency + minimum: 0 + example: 100 + maxAmount: + type: integer + format: int64 + description: Maximum amount that can be sent in the smallest unit of this currency + minimum: 0 + example: 1000000 + requiredCounterpartyFields: type: array items: - $ref: '#/components/schemas/IndividualSourceOfFundsCategory' - description: Structured source-of-funds categories (FLOW of funds for this account). + $ref: '#/components/schemas/CounterpartyFieldDefinition' + description: List of fields which the platform requires from the counterparty institutions about counterparty customers. Platforms can set mandatory to false if the platform does not require the field, but would like to have it available. Some fields may be required by the underlying UMA provider. example: - - SALARY - sourceOfFundsOtherDescription: - type: string - description: Free-form description of the customer's source of funds. Required when `sourceOfFundsCategories` includes `OTHER`; otherwise omitted. - example: Contest winnings - sourceOfWealthCategories: + - name: FULL_NAME + mandatory: true + - name: BIRTH_DATE + mandatory: true + - name: NATIONALITY + mandatory: true + providerRequiredCustomerFields: type: array items: - $ref: '#/components/schemas/SourceOfWealthCategory' - description: Structured source-of-wealth categories (STOCK — origin of accumulated wealth). - example: - - SALARY - - INVESTMENTS - sourceOfWealthOtherDescription: - type: string - description: Free-form description of the customer's source of wealth. Required when `sourceOfWealthCategories` includes `OTHER`; otherwise omitted. - example: Royalty income from published works - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - purposeOfAccountOtherDescription: - type: string - description: Free-form description of the customer's intended purpose for the Grid account. Required when `purposeOfAccount` is `OTHER`; otherwise omitted. - example: Household budgeting between spouses - expectedMonthlyTransactionCount: - $ref: '#/components/schemas/ExpectedMonthlyTransactionCount' - expectedMonthlyTransactionVolume: - $ref: '#/components/schemas/ExpectedMonthlyTransactionVolume' - annualIncomeRange: - $ref: '#/components/schemas/AnnualIncomeRange' - netWorthRange: - $ref: '#/components/schemas/NetWorthRange' - pepStatus: - $ref: '#/components/schemas/PepStatus' - IndividualCustomerFields: - allOf: - - type: object - required: - - customerType - properties: - customerType: - type: string - enum: - - INDIVIDUAL - kycStatus: - $ref: '#/components/schemas/KycStatus' - fullName: - type: string - description: Individual's full name - example: John Michael Doe - birthDate: - type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1990-01-15' - nationality: - type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - address: - $ref: '#/components/schemas/Address' - taxIdType: - $ref: '#/components/schemas/IdentificationType' - taxIdentifier: - type: string - description: Tax-identification number. For US persons this is the SSN (format `###-##-####`) or ITIN. For non-US persons this is the tax number issued by `taxIdCountryOfIssuance`. - example: 123-45-6789 - taxIdCountryOfIssuance: - type: string - description: Country that issued the tax identifier (ISO 3166-1 alpha-2). Required when `taxIdType` is `NON_US_TAX_ID`. - example: US - - $ref: '#/components/schemas/CustomerEdd' - IndividualCustomer: - title: Individual Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/IndividualCustomerFields' - KybStatus: - type: string - enum: - - UNVERIFIED - - PENDING - - APPROVED - - REJECTED - - HOLD - description: The current KYB status of a business customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. - example: APPROVED - EntityType: - type: string - enum: - - SOLE_PROPRIETORSHIP - - PARTNERSHIP - - LLC - - CORPORATION - - S_CORPORATION - - NON_PROFIT - - PUBLICLY_LISTED_COMPANY - - TRUST - - PRIVATE_FOUNDATION - - CHARITY - - OTHER - description: Legal entity type of the business - example: LLC - BusinessType: - type: string - description: The high-level industry category of the business - enum: - - AGRICULTURE_FORESTRY_FISHING_AND_HUNTING - - MINING_QUARRYING_AND_OIL_AND_GAS_EXTRACTION - - UTILITIES - - CONSTRUCTION - - MANUFACTURING - - WHOLESALE_TRADE - - RETAIL_TRADE - - TRANSPORTATION_AND_WAREHOUSING - - INFORMATION - - FINANCE_AND_INSURANCE - - REAL_ESTATE_AND_RENTAL_AND_LEASING - - PROFESSIONAL_SCIENTIFIC_AND_TECHNICAL_SERVICES - - MANAGEMENT_OF_COMPANIES_AND_ENTERPRISES - - ADMINISTRATIVE_AND_SUPPORT_AND_WASTE_MANAGEMENT_AND_REMEDIATION_SERVICES - - EDUCATIONAL_SERVICES - - HEALTH_CARE_AND_SOCIAL_ASSISTANCE - - ARTS_ENTERTAINMENT_AND_RECREATION - - ACCOMMODATION_AND_FOOD_SERVICES - - OTHER_SERVICES - - PUBLIC_ADMINISTRATION - SourceOfFundsCategory: - type: string - enum: - - OPERATING_REVENUE - - INVESTMENT_INCOME - - LOANS - - VENTURE_CAPITAL - - PERSONAL_SAVINGS - - DONATIONS - - OTHER - description: A structured source-of-funds category for the business - example: OPERATING_REVENUE - BusinessInfoUpdate: + $ref: '#/components/schemas/CustomerInfoFieldName' + description: List of customer info field names that are required by the underlying UMA provider when creating a customer for this currency. These fields must be supplied when creating or updating a customer if this currency is intended to be used by that customer. If no fields are required, this field is omitted. + readOnly: true + example: + - NATIONALITY + - BIRTH_DATE + providerRequiredCounterpartyCustomerFields: + type: array + items: + $ref: '#/components/schemas/CustomerInfoFieldName' + description: List of fields that are required by the underlying UMA provider for this currency. If the counterparty does not provide these fields, quote requests will fail. + readOnly: true + example: + - FULL_NAME + - COUNTRY_OF_RESIDENCE + enabledTransactionTypes: + type: array + items: + $ref: '#/components/schemas/TransactionType' + description: List of transaction types that are enabled for this currency. + example: + - OUTGOING + - INCOMING + required: + - currencyCode + - minAmount + - maxAmount + - requiredCounterpartyFields + - enabledTransactionTypes + EmbeddedWalletConfig: type: object - description: Additional information for business entities + description: | + Per-platform embedded-wallet configuration. Controls branding and OTP + behavior for the email sent when a customer authenticates with an + EMAIL_OTP credential. Fields omitted from a request are left unchanged. properties: - legalName: - type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: + appName: type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: + maxLength: 255 + description: App name displayed in the default OTP email template. + example: Acme Wallet + otpLength: + type: integer + minimum: 4 + maximum: 12 + description: | + Number of digits / characters in the OTP code. Defaults to 6 when + not set. + example: 6 + alphanumeric: + type: boolean + description: | + If true, OTP includes letters in addition to digits. Defaults to + numeric-only when not set. + example: false + expirationSeconds: + type: integer + minimum: 1 + maximum: 86400 + description: | + OTP validity window in seconds. Defaults to 300 when not set. + example: 300 + sendFromEmailAddress: type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: + format: email + maxLength: 255 + description: Custom sender email address for OTP emails. + example: noreply@acme.com + sendFromEmailSenderName: type: string - description: Business registration number - example: '5523041' - incorporatedOn: + maxLength: 255 + description: | + Custom sender display name. Defaults to "Notifications" when not set. + example: Acme Notifications + replyToEmailAddress: type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: + format: email + maxLength: 255 + description: Custom reply-to email address for OTP emails. + example: support@acme.com + logoUrl: type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: - type: array - items: - type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) - example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: + format: uri + maxLength: 512 + description: URL to a PNG logo for the OTP email. Resized to 340x124px. + example: https://acme.com/logo.png + PlatformConfig: + type: object + properties: + id: type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: + description: System-generated unique identifier + readOnly: true + example: PlatformConfig:019542f5-b3e7-1d02-0000-000000000003 + umaDomain: type: string - enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + description: UMA domain for this platform + example: platform.uma.domain + proxyUmaSubdomain: type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + description: The subdomain that incoming requests will be proxied to + example: platform + webhookEndpoint: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: + description: URL where webhook notifications will be sent + example: https://api.mycompany.com/webhooks/uma + supportedCurrencies: type: array items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + $ref: '#/components/schemas/PlatformCurrencyConfig' + description: | + List of currencies supported by the platform. This is what the platform's + customers are able to hold, send, and receive. + isRegulatedFinancialInstitution: + type: boolean + description: | + Whether the platform is a regulated financial institution. This is used to + determine if the platform's customers must be KYC/KYB'd by Lightspark via + the KYC link flow. This can only be set by Lightspark during platform + creation. + example: false + embeddedWalletConfig: + $ref: '#/components/schemas/EmbeddedWalletConfig' + description: | + Embedded-wallet branding and OTP settings for this platform. Present + only when the platform has configured embedded-wallet support; + omitted otherwise. + createdAt: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + format: date-time + description: Creation timestamp + readOnly: true + example: '2025-06-15T12:30:45Z' + updatedAt: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: - type: array - items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BusinessCustomerFields: + format: date-time + description: Last update timestamp + readOnly: true + example: '2025-06-15T12:30:45Z' + Error401: type: object required: - - customerType + - message + - status + - code properties: - customerType: + status: + type: integer + enum: + - 401 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | UNAUTHORIZED | Issue with API credentials | + | INVALID_SIGNATURE | Signature header is invalid | + | WALLET_SIGNATURE_MISSING | The `Grid-Wallet-Signature` header is required for this Embedded Wallet action but was not supplied | + | WALLET_SIGNATURE_MALFORMED | The `Grid-Wallet-Signature` header could not be parsed (bad encoding, structure, or fields) | + | WALLET_SIGNATURE_BODY_MISMATCH | The `Grid-Wallet-Signature` was computed over a different request body than the one received | + | WALLET_SIGNATURE_INVALID | The `Grid-Wallet-Signature` failed cryptographic verification against the registered credential | + | REQUEST_ID_MISSING | The `Request-Id` header is required on the signed retry but was not supplied (paired with `Grid-Wallet-Signature`) | enum: - - BUSINESS - kybStatus: - $ref: '#/components/schemas/KybStatus' - address: - $ref: '#/components/schemas/Address' - businessInfo: - $ref: '#/components/schemas/BusinessInfoUpdate' - BusinessInfoResponse: + - UNAUTHORIZED + - INVALID_SIGNATURE + - WALLET_SIGNATURE_MISSING + - WALLET_SIGNATURE_MALFORMED + - WALLET_SIGNATURE_BODY_MISMATCH + - WALLET_SIGNATURE_INVALID + - REQUEST_ID_MISSING + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error500: type: object - description: | - Business information returned on a customer. `taxId` and `incorporatedOn` are - required on creation but may be absent on legacy customers that pre-date the - requirement, so both are optional in responses. required: - - legalName + - message + - status + - code properties: - legalName: - type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: - type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: - type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: - type: string - description: Business registration number - example: '5523041' - incorporatedOn: - type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: - type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: - type: array - items: - type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) - example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: - type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: - type: string + status: + type: integer enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - 500 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | GRID_SWITCH_ERROR | Grid switch error | + | INTERNAL_ERROR | Internal server or UMA error | enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + - GRID_SWITCH_ERROR + - INTERNAL_ERROR + message: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + PlatformConfigUpdateRequest: + type: object + properties: + umaDomain: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + example: mycompany.com + webhookEndpoint: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: + example: https://api.mycompany.com/webhooks/uma + supportedCurrencies: type: array items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BeneficialOwnerRole: - type: string - enum: - - UBO - - DIRECTOR - - COMPANY_OFFICER - - CONTROL_PERSON - - TRUSTEE - - GENERAL_PARTNER - description: Role of the beneficial owner within the business - example: UBO - BeneficialOwnerPersonalInfo: + $ref: '#/components/schemas/PlatformCurrencyConfig' + embeddedWalletConfig: + $ref: '#/components/schemas/EmbeddedWalletConfig' + description: | + Update or create the embedded-wallet configuration for this platform. + Fields omitted from the nested object are left unchanged. Omit this + field at the top level to leave the embedded-wallet configuration + unchanged entirely. + Error400: type: object required: - - firstName - - lastName - - birthDate - - nationality - - address - - idType - - identifier + - message + - status + - code properties: - firstName: - type: string - description: First name of the individual - example: Jane - middleName: - type: string - description: Middle name of the individual - example: Marie - lastName: + status: + type: integer + enum: + - 400 + description: HTTP status code + code: type: string - description: Last name of the individual - example: Smith - birthDate: + description: | + | Error Code | Description | + |------------|-------------| + | INVALID_INPUT | Invalid input provided | + | MISSING_MANDATORY_USER_INFO | Required customer information is missing | + | INVITATION_ALREADY_CLAIMED | Invitation has already been claimed | + | INVITATIONS_NOT_CONFIGURED | Invitations are not configured | + | INVALID_UMA_ADDRESS | UMA address format is invalid | + | INVITATION_CANCELLED | Invitation has been cancelled | + | QUOTE_REQUEST_FAILED | An issue occurred during the quote process; this is retryable | + | INVALID_PAYREQ_RESPONSE | Counterparty Payreq response was invalid | + | INVALID_RECEIVER | Receiver is invalid | + | PARSE_PAYREQ_RESPONSE_ERROR | Error parsing receiver PayReq response | + | CERT_CHAIN_INVALID | Counterparty certificate chain is invalid | + | CERT_CHAIN_EXPIRED | Counterparty certificate chain has expired | + | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | + | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | + | SENDER_NOT_ACCEPTED | Sender is not accepted | + | AMOUNT_OUT_OF_RANGE | Amount is out of range | + | INVALID_CURRENCY | Currency is invalid | + | INVALID_TIMESTAMP | Timestamp is invalid | + | INVALID_NONCE | Nonce is invalid | + | INVALID_REQUEST_FORMAT | Request format is invalid | + | INVALID_BANK_ACCOUNT | Bank account is invalid | + | SELF_PAYMENT | Self payment not allowed | + | LOOKUP_REQUEST_FAILED | Lookup request failed | + | PARSE_LNURLP_RESPONSE_ERROR | Error parsing LNURLP response | + | INVALID_AMOUNT | Amount is invalid | + | WEBHOOK_ENDPOINT_NOT_SET | Webhook endpoint is not set | + | WEBHOOK_DELIVERY_ERROR | Webhook delivery error | + | LOW_QUALITY | Document quality too low to process | + | DATA_MISMATCH | Document details don't match provided information | + | EXPIRED | Document has expired | + | SUSPECTED_FRAUD | Document suspected of being forged or edited | + | UNSUITABLE_DOCUMENT | Document type is not accepted or not supported | + | INCOMPLETE | Document is missing pages or sides | + | EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS | An EMAIL_OTP credential is already registered on the target internal account; only one email OTP credential is supported per internal account at this time | + | SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time | + | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account | + | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | + | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | + | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | + enum: + - INVALID_INPUT + - MISSING_MANDATORY_USER_INFO + - INVITATION_ALREADY_CLAIMED + - INVITATIONS_NOT_CONFIGURED + - INVALID_UMA_ADDRESS + - INVITATION_CANCELLED + - QUOTE_REQUEST_FAILED + - INVALID_PAYREQ_RESPONSE + - INVALID_RECEIVER + - PARSE_PAYREQ_RESPONSE_ERROR + - CERT_CHAIN_INVALID + - CERT_CHAIN_EXPIRED + - INVALID_PUBKEY_FORMAT + - MISSING_REQUIRED_UMA_PARAMETERS + - SENDER_NOT_ACCEPTED + - AMOUNT_OUT_OF_RANGE + - INVALID_CURRENCY + - INVALID_TIMESTAMP + - INVALID_NONCE + - INVALID_REQUEST_FORMAT + - INVALID_BANK_ACCOUNT + - SELF_PAYMENT + - LOOKUP_REQUEST_FAILED + - PARSE_LNURLP_RESPONSE_ERROR + - INVALID_AMOUNT + - WEBHOOK_ENDPOINT_NOT_SET + - WEBHOOK_DELIVERY_ERROR + - LOW_QUALITY + - DATA_MISMATCH + - EXPIRED + - SUSPECTED_FRAUD + - UNSUITABLE_DOCUMENT + - INCOMPLETE + - EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS + - SMS_OTP_CREDENTIAL_ALREADY_EXISTS + - PASSKEY_CREDENTIAL_ALREADY_EXISTS + - STABLECOIN_PROVIDER_ACCOUNT_INVALID + - STABLECOIN_PROVIDER_ACCOUNT_REVOKED + - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED + message: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1978-06-15' - nationality: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error501: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 501 + description: HTTP status code + code: type: string - description: Country of nationality (ISO 3166-1 alpha-2) - example: US - email: + description: | + | Error Code | Description | + |------------|-------------| + | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory payee data key | + | NOT_IMPLEMENTED | Feature not implemented | + enum: + - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY + - NOT_IMPLEMENTED + message: type: string - format: email - description: Email address of the individual - example: jane.smith@acmecorp.com - phoneNumber: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Currency: + type: object + properties: + code: type: string - description: Phone number in E.164 format - example: '+14155550192' - pattern: ^\+[1-9]\d{1,14}$ - address: - $ref: '#/components/schemas/Address' - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: type: string - description: The identification number or value - example: 123-45-6789 - countryOfIssuance: + description: Full name of the currency + example: United States Dollar + symbol: type: string - description: Country that issued the identification (ISO 3166-1 alpha-2) - example: US - BeneficialOwner: + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + PaymentRail: + type: string + enum: + - ACH + - ACH_COLOMBIA + - BANK_TRANSFER + - BRE_B + - CIPS + - FAST + - FASTER_PAYMENTS + - FEDNOW + - INSTAPAY + - MOBILE_MONEY + - NEFT + - PAYNOW + - PESONET + - PIX + - RTGS + - RTP + - SEPA + - SEPA_INSTANT + - SPEI + - SWIFT + - UNIONPAY + - UPI + - WIRE + description: The payment rail used for the transfer. Payment rails represent the underlying payment network or system used to move funds between accounts. + example: ACH + ExchangeRateFees: + type: object + description: Fees associated with an exchange rate + properties: + fixed: + type: integer + format: int64 + description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) + minimum: 0 + example: 100 + total: + type: integer + format: int64 + description: Total fees in the smallest unit of the sending currency (e.g., cents for USD). This value may change depending on the sending amount used; if no sending amount is specified, it falls back to the default. + minimum: 0 + example: 100 + ExchangeRate: type: object + description: Exchange rate information for a currency corridor required: - - id - - customerId - - roles - - ownershipPercentage - - personalInfo - - kycStatus - - createdAt + - sourceCurrency + - destinationCurrency + - destinationPaymentRail + - minSendingAmount + - maxSendingAmount + - sendingAmount + - receivingAmount + - exchangeRate + - fees + - updatedAt properties: - id: - type: string - description: Unique identifier for this beneficial owner - example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 - customerId: - type: string - description: The ID of the business customer this beneficial owner is associated with - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - roles: - type: array - items: - $ref: '#/components/schemas/BeneficialOwnerRole' - description: Roles of this person within the business - example: - - UBO - - DIRECTOR - ownershipPercentage: + sourceCurrency: + $ref: '#/components/schemas/Currency' + sendingAmount: type: integer - description: Percentage of ownership in the business (0-100) + format: int64 + description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. minimum: 0 - maximum: 100 - example: 51 - personalInfo: - $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' - kycStatus: - $ref: '#/components/schemas/KycStatus' - createdAt: - type: string - format: date-time - description: When this beneficial owner was created - example: '2025-10-03T12:00:00Z' + example: 10000 + minSendingAmount: + type: integer + format: int64 + description: The minimum supported sending amount in the smallest unit of the source currency. + minimum: 0 + example: 100 + maxSendingAmount: + type: integer + format: int64 + description: The maximum supported sending amount in the smallest unit of the source currency. + minimum: 0 + example: 10000000 + destinationCurrency: + $ref: '#/components/schemas/Currency' + destinationPaymentRail: + allOf: + - $ref: '#/components/schemas/PaymentRail' + - description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) + example: UPI + receivingAmount: + type: integer + format: int64 + description: The receiving amount in the smallest unit of the destination currency + minimum: 0 + example: 1650000 + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 0.012121 + fees: + $ref: '#/components/schemas/ExchangeRateFees' updatedAt: type: string format: date-time - description: When this beneficial owner was last updated - example: '2025-10-03T12:00:00Z' - BusinessCustomer: - title: Business Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfoResponse' - beneficialOwners: - type: array - items: - $ref: '#/components/schemas/BeneficialOwner' - CustomerOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomer' - - $ref: '#/components/schemas/BusinessCustomer' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomer' - BUSINESS: '#/components/schemas/BusinessCustomer' - CustomerListResponse: + description: Timestamp when this exchange rate was last refreshed + example: '2025-02-05T12:00:00Z' + ExchangeRateListResponse: type: object required: - data - - hasMore properties: data: type: array - description: List of customers matching the filter criteria + description: List of exchange rates matching the filter criteria items: - $ref: '#/components/schemas/CustomerOneOf' - hasMore: - type: boolean - description: Indicates if more results are available beyond this page - nextCursor: - type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) - totalCount: - type: integer - description: Total number of customers matching the criteria (excluding pagination) - Error405: + $ref: '#/components/schemas/ExchangeRate' + DiscoveryListResponse: + type: object + properties: + data: + type: array + description: List of payment rails matching the filter criteria + items: + type: object + required: + - bankName + - displayName + - country + - currency + properties: + bankName: + type: string + description: | + Canonical name for this payment rail. Pass this value as `bankName` when creating an external account. + displayName: + type: string + description: Human-friendly display name for this payment rail. + country: + type: string + description: ISO 3166-1 alpha-2 country code. + currency: + type: string + description: ISO 4217 currency code. + CustomerType: + type: string + enum: + - INDIVIDUAL + - BUSINESS + description: Whether the customer is an individual or a business entity + example: INDIVIDUAL + ContactVerificationStatus: + type: string + description: Status of an individual contact-verification channel (email or phone). `PENDING` means verification is required but not yet completed; `VERIFIED` means the channel has been confirmed. + enum: + - PENDING + - VERIFIED + example: VERIFIED + ContactVerification: type: object - required: - - message - - status - - code + description: |- + Email and/or phone verification state for the customer. This object is **only present when the customer's regulatory jurisdiction requires contact verification** (e.g. EU customers). For customers who have no such requirement, this object is omitted entirely — no action is needed. + + Each channel is reported independently: only the channels the customer's provider actually requires are present. A provider may require both email and phone, just one of them, or — when the object is absent — neither. Every channel that **is** present must reach `VERIFIED` before the customer can begin KYC. Drive each present channel with `POST /customers/{customerId}/verify-email` and/or `POST /customers/{customerId}/verify-phone` (and their `/confirm` sub-routes). properties: - status: - type: integer - enum: - - 405 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | - enum: - - METHOD_NOT_ALLOWED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerCreateRequest: + email: + $ref: '#/components/schemas/ContactVerificationStatus' + description: Verification status of the customer's email address. Present only when the provider requires email verification. + phone: + $ref: '#/components/schemas/ContactVerificationStatus' + description: Verification status of the customer's phone number. Present only when the provider requires phone verification. + Customer: type: object required: + - umaAddress + - platformCustomerId - customerType properties: + id: + type: string + description: System-generated unique identifier + readOnly: true + example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific customer identifier. If not provided, one will be generated by the system. + description: Platform-specific customer identifier example: 9f84e0c2a72c4fa customerType: $ref: '#/components/schemas/CustomerType' region: type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity and regulatory jurisdiction. example: US currencies: type: array items: type: string - description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. + description: List of currency codes enabled for this customer. example: - USD - USDC email: type: string format: email - description: Email address for the customer. **Required in regions that verify the email address before identity verification** (e.g. the EU); optional otherwise. + description: Email address for the customer. example: john.doe@example.com phoneNumber: type: string pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. **Required in regions that verify the phone number before identity verification** (e.g. the EU); optional otherwise. + description: Phone number for the customer in strict E.164 format. example: '+14155551234' umaAddress: type: string - description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + description: Full UMA address (always present in responses, even if system-generated). This is an optional identifier to route payments to the customer. example: $john.doe@uma.domain.com - IndividualCustomerCreateRequest: - title: Individual Customer Create Request + createdAt: + type: string + format: date-time + description: Creation timestamp + readOnly: true + example: '2025-07-21T17:32:28Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp + readOnly: true + example: '2025-07-21T17:32:28Z' + isDeleted: + type: boolean + description: Whether the customer is marked as deleted + example: false + readOnly: true + contactVerification: + allOf: + - $ref: '#/components/schemas/ContactVerification' + readOnly: true + description: Email and phone verification state. **Only present when the customer's payment provider requires it** (e.g. EU customers); omitted otherwise. + KycStatus: + type: string + enum: + - UNVERIFIED + - PENDING + - APPROVED + - REJECTED + - HOLD + description: The current KYC status of a customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. + example: APPROVED + Address: + type: object + required: + - line1 + - postalCode + - country + properties: + line1: + type: string + description: Street address line 1 + example: 123 Main Street + line2: + type: string + description: Street address line 2 + example: Apt 4B + city: + type: string + description: City + example: San Francisco + state: + type: string + description: State/Province/Region + example: CA + postalCode: + type: string + description: Postal/ZIP code + example: '94105' + country: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + IndividualCustomerFields: + type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - INDIVIDUAL + kycStatus: + $ref: '#/components/schemas/KycStatus' + fullName: + type: string + description: Individual's full name + example: John Michael Doe + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1990-01-15' + nationality: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + address: + $ref: '#/components/schemas/Address' + IndividualCustomer: + title: Individual Customer allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/Customer' - $ref: '#/components/schemas/IndividualCustomerFields' - - type: object - properties: - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - writeOnly: true - description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. - example: 123-45-6789 - BusinessInfo: + KybStatus: + type: string + enum: + - UNVERIFIED + - PENDING + - APPROVED + - REJECTED + - HOLD + description: The current KYB status of a business customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. + example: APPROVED + EntityType: + type: string + enum: + - SOLE_PROPRIETORSHIP + - PARTNERSHIP + - LLC + - CORPORATION + - S_CORPORATION + - NON_PROFIT + - OTHER + description: Legal entity type of the business + example: LLC + BusinessType: + type: string + description: The high-level industry category of the business + enum: + - AGRICULTURE_FORESTRY_FISHING_AND_HUNTING + - MINING_QUARRYING_AND_OIL_AND_GAS_EXTRACTION + - UTILITIES + - CONSTRUCTION + - MANUFACTURING + - WHOLESALE_TRADE + - RETAIL_TRADE + - TRANSPORTATION_AND_WAREHOUSING + - INFORMATION + - FINANCE_AND_INSURANCE + - REAL_ESTATE_AND_RENTAL_AND_LEASING + - PROFESSIONAL_SCIENTIFIC_AND_TECHNICAL_SERVICES + - MANAGEMENT_OF_COMPANIES_AND_ENTERPRISES + - ADMINISTRATIVE_AND_SUPPORT_AND_WASTE_MANAGEMENT_AND_REMEDIATION_SERVICES + - EDUCATIONAL_SERVICES + - HEALTH_CARE_AND_SOCIAL_ASSISTANCE + - ARTS_ENTERTAINMENT_AND_RECREATION + - ACCOMMODATION_AND_FOOD_SERVICES + - OTHER_SERVICES + - PUBLIC_ADMINISTRATION + PurposeOfAccount: + type: string + enum: + - CONTRACTOR_PAYOUTS + - CREATOR_PAYOUTS + - EMPLOYEE_PAYOUTS + - MARKETPLACE_SELLER_PAYOUTS + - SUPPLIER_PAYMENTS + - CROSS_BORDER_B2B + - AR_AUTOMATION + - AP_AUTOMATION + - EMBEDDED_PAYMENTS + - PLATFORM_FEE_COLLECTION + - P2P_TRANSFERS + - CHARITABLE_DONATIONS + - OTHER + description: The intended purpose for using the Grid account + example: CONTRACTOR_PAYOUTS + SourceOfFundsCategory: + type: string + enum: + - OPERATING_REVENUE + - INVESTMENT_INCOME + - LOANS + - VENTURE_CAPITAL + - PERSONAL_SAVINGS + - DONATIONS + - OTHER + description: A structured source-of-funds category for the business + example: OPERATING_REVENUE + BusinessInfoUpdate: type: object - description: Additional information required for business entities - required: - - legalName - - taxId - - incorporatedOn + description: Additional information for business entities properties: legalName: type: string @@ -12570,641 +10216,533 @@ components: description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) example: - US - BusinessCustomerCreateRequest: - title: Business Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - required: - - businessInfo - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfo' - CustomerCreateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerCreateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' - Error409: + BusinessCustomerFields: type: object required: - - message - - status - - code + - customerType properties: - status: - type: integer - enum: - - 409 - description: HTTP status code - code: + customerType: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | - | TRANSACTION_NOT_CANCELLABLE | Transaction has already settled or is otherwise past the point where it can be cancelled | - | UMA_ADDRESS_EXISTS | UMA address already exists | - | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | - | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | - | PASSKEY_ALREADY_ENROLLED | The customer already has an enrolled passkey factor; only one passkey per customer is supported. Delete the existing one before enrolling another | - | SCA_SESSION_REQUIRED | The customer's Strong Customer Authentication login session is missing or expired. Re-authenticate the customer, then retry the request. Distinct from a `401`, which means the platform's own API credentials were rejected | - | BENEFICIARY_TRUSTED | The external account is currently a trusted beneficiary, so it cannot be deleted. Untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete | - | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | enum: - - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL - - TRANSACTION_NOT_CANCELLABLE - - UMA_ADDRESS_EXISTS - - EMAIL_OTP_EMAIL_ALREADY_EXISTS - - EMAIL_OTP_CREDENTIAL_SET_CHANGED - - PASSKEY_ALREADY_ENROLLED - - SCA_SESSION_REQUIRED - - BENEFICIARY_TRUSTED - - CONFLICT - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error404: + - BUSINESS + kybStatus: + $ref: '#/components/schemas/KybStatus' + address: + $ref: '#/components/schemas/Address' + businessInfo: + $ref: '#/components/schemas/BusinessInfoUpdate' + BusinessInfoResponse: type: object + description: | + Business information returned on a customer. `taxId` and `incorporatedOn` are + required on creation but may be absent on legacy customers that pre-date the + requirement, so both are optional in responses. required: - - message - - status - - code + - legalName properties: - status: - type: integer - enum: - - 404 - description: HTTP status code - code: + legalName: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_FOUND | Transaction not found | - | INVITATION_NOT_FOUND | Invitation not found | - | USER_NOT_FOUND | Customer not found | - | QUOTE_NOT_FOUND | Quote not found | - | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | - | TOKEN_NOT_FOUND | Token not found | - | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | - | REFERENCE_NOT_FOUND | Reference not found | - | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | - | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | - enum: - - TRANSACTION_NOT_FOUND - - INVITATION_NOT_FOUND - - USER_NOT_FOUND - - QUOTE_NOT_FOUND - - LOOKUP_REQUEST_NOT_FOUND - - TOKEN_NOT_FOUND - - BULK_UPLOAD_JOB_NOT_FOUND - - REFERENCE_NOT_FOUND - - UMA_NOT_FOUND - - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND - message: + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error410: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 410 - description: HTTP status code - code: + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: type: string - description: | - | Error Code | Description | - |------------|-------------| - | CUSTOMER_DELETED | Customer has been permanently deleted | - enum: - - CUSTOMER_DELETED - message: + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerUpdateRequest: - title: Customer Update Request - description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. - type: object - required: - - customerType - properties: - customerType: - $ref: '#/components/schemas/CustomerType' - currencies: + description: Business registration number + example: '5523041' + incorporatedOn: + type: string + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: + type: string + description: Tax identification number + example: 47-1234567 + countriesOfOperation: type: array items: type: string - description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - USD - - EUR - - USDC - email: - type: string - format: email - description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. - example: '+14155551234' - umaAddress: - type: string - description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerUpdateRequest: - title: Individual Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - BusinessCustomerUpdateRequest: - title: Business Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - CustomerUpdateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - SignedRequestChallenge: - title: Signed Request Challenge - type: object - required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. - properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' - requestId: - type: string - description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: - type: string - format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' - Error424: - type: object - required: - - message - - status - - code - properties: - status: - type: integer + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: + type: string enum: - - 424 - description: HTTP status code - code: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: type: string - description: | - | Error Code | Description | - |------------|-------------| - | PAYREQ_REQUEST_FAILED | Payment request failed | - | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | - | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | - | LNURLP_REQUEST_FAILED | LNURLP request failed | - | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | enum: - - PAYREQ_REQUEST_FAILED - - COUNTERPARTY_PUBKEY_FETCH_ERROR - - NO_COMPATIBLE_UMA_VERSION - - LNURLP_REQUEST_FAILED - - EMAIL_OTP_CREDENTIAL_SYNC_FAILED - message: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - KycLinkCreateRequest: - type: object - description: Request body for generating a hosted KYC link for an existing customer. - properties: - redirectUri: + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: type: string - format: uri - description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. - example: https://app.example.com/onboarding/completed - KycProvider: + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BeneficialOwnerRole: type: string - description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. enum: - - SUMSUB - example: SUMSUB - KycLinkResponse: + - UBO + - DIRECTOR + - COMPANY_OFFICER + - CONTROL_PERSON + - TRUSTEE + - GENERAL_PARTNER + description: Role of the beneficial owner within the business + example: UBO + IdentificationType: + type: string + enum: + - SSN + - ITIN + - EIN + - NON_US_TAX_ID + description: Type of tax identification + example: SSN + BeneficialOwnerPersonalInfo: type: object - description: A hosted KYC link that the customer can complete to verify their identity. required: - - kycUrl - - expiresAt - - provider + - firstName + - lastName + - birthDate + - nationality + - address + - idType + - identifier properties: - kycUrl: + firstName: type: string - description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. - example: https://kyc.lightspark.com/onboard/abc123def456 - expiresAt: + description: First name of the individual + example: Jane + middleName: type: string - format: date-time - description: Time at which the hosted link expires and can no longer be used. - example: '2027-01-15T14:32:00Z' - provider: - $ref: '#/components/schemas/KycProvider' - token: + description: Middle name of the individual + example: Marie + lastName: type: string - description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. - example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ContactVerificationConfirmRequest: - type: object - description: Request body for confirming an email or phone verification challenge. - required: - - code - properties: - code: + description: Last name of the individual + example: Smith + birthDate: type: string - description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. - example: '123456' - ScaFactor: - type: string - enum: - - SMS_OTP - - TOTP - - PASSKEY - description: | - A Strong Customer Authentication factor. - - | Factor | Description | - |--------|-------------| - | `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. | - | `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). | - | `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. | - ScaFactorView: + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1978-06-15' + nationality: + type: string + description: Country of nationality (ISO 3166-1 alpha-2) + example: US + email: + type: string + format: email + description: Email address of the individual + example: jane.smith@acmecorp.com + phoneNumber: + type: string + description: Phone number in E.164 format + example: '+14155550192' + pattern: ^\+[1-9]\d{1,14}$ + address: + $ref: '#/components/schemas/Address' + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + description: The identification number or value + example: 123-45-6789 + countryOfIssuance: + type: string + description: Country that issued the identification (ISO 3166-1 alpha-2) + example: US + BeneficialOwner: type: object - description: An enrolled Strong Customer Authentication factor. required: - - factor + - id + - customerId + - roles + - ownershipPercentage + - personalInfo + - kycStatus + - createdAt properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The kind of enrolled factor. - credentialId: + id: type: string - description: The per-credential id, populated only for `PASSKEY` factors (the id passed to delete a passkey). Omitted for `TOTP` and `SMS_OTP`, which have no per-credential id. - name: + description: Unique identifier for this beneficial owner + example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 + customerId: + type: string + description: The ID of the business customer this beneficial owner is associated with + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + roles: + type: array + items: + $ref: '#/components/schemas/BeneficialOwnerRole' + description: Roles of this person within the business + example: + - UBO + - DIRECTOR + ownershipPercentage: + type: integer + description: Percentage of ownership in the business (0-100) + minimum: 0 + maximum: 100 + example: 51 + personalInfo: + $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' + kycStatus: + $ref: '#/components/schemas/KycStatus' + createdAt: + type: string + format: date-time + description: When this beneficial owner was created + example: '2025-10-03T12:00:00Z' + updatedAt: type: string - description: An optional human-readable label for this factor. - ScaFactorList: + format: date-time + description: When this beneficial owner was last updated + example: '2025-10-03T12:00:00Z' + BusinessCustomer: + title: Business Customer + allOf: + - $ref: '#/components/schemas/Customer' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfoResponse' + beneficialOwners: + type: array + items: + $ref: '#/components/schemas/BeneficialOwner' + CustomerOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomer' + - $ref: '#/components/schemas/BusinessCustomer' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomer' + BUSINESS: '#/components/schemas/BusinessCustomer' + CustomerListResponse: type: object - description: The Strong Customer Authentication factors a customer has enrolled. required: - - factors + - data + - hasMore properties: - factors: + data: type: array - description: The customer's enrolled SCA factors. + description: List of customers matching the filter criteria items: - $ref: '#/components/schemas/ScaFactorView' - TotpFactorEnrollRequest: - type: object - title: TOTP Factor Enroll Request - description: Start enrolling a time-based one-time-password (TOTP) authenticator factor. - required: - - type - properties: - type: + $ref: '#/components/schemas/CustomerOneOf' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: type: string - enum: - - TOTP - description: Discriminator selecting the TOTP factor. TOTP enrollment needs no other input at start. - PasskeyFactorEnrollRequest: + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of customers matching the criteria (excluding pagination) + Error405: type: object - title: Passkey Factor Enroll Request - description: Start enrolling a WebAuthn passkey factor. required: - - type + - message + - status + - code properties: - type: - type: string + status: + type: integer enum: - - PASSKEY - description: Discriminator selecting the passkey factor. Passkey enrollment needs no other input at start. - ScaFactorEnrollRequestOneOf: - oneOf: - - $ref: '#/components/schemas/TotpFactorEnrollRequest' - - $ref: '#/components/schemas/PasskeyFactorEnrollRequest' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpFactorEnrollRequest' - PASSKEY: '#/components/schemas/PasskeyFactorEnrollRequest' - description: Which SCA factor to begin enrolling, selected by `type`. `SMS_OTP` is not enrollable (it uses the customer's verified phone), so only `TOTP` and `PASSKEY` are valid here. - TotpEnrollmentStart: - type: object - description: The shared secret a customer's authenticator app needs to enroll a TOTP factor. Returned by `POST /sca/factors` for a `TOTP` request; the customer scans `totpUri` (an `otpauth://` provisioning URI) and confirms with the first code their app produces. - required: - - type - - secret - - secretBase32Encoded - - totpUri - properties: - type: + - 405 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | enum: - - TOTP - description: Discriminator identifying this as the TOTP enrollment-start payload. - secret: - type: string - description: The raw TOTP shared secret. - secretBase32Encoded: - type: string - description: The Base32-encoded shared secret, suitable for manual entry into an authenticator app that does not scan QR codes. - totpUri: + - METHOD_NOT_ALLOWED + message: type: string - description: The `otpauth://` provisioning URI (the QR-code payload) the customer's authenticator app scans to enroll the factor. - example: otpauth://totp/Grid:customer@example.com?secret=ABC123&issuer=Grid - PasskeyEnrollmentStart: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerCreateRequest: type: object - description: Opaque WebAuthn registration options relayed to the end user's device to enroll a passkey factor. Grid performs no crypto; pass `options` to the device's WebAuthn API to produce a credential, then submit that credential to the confirm endpoint unmodified. required: - - type - - options - - allowedOrigins - - relyingPartyId + - customerType properties: - type: + platformCustomerId: type: string - enum: - - PASSKEY - description: Discriminator identifying this as the passkey enrollment-start payload. - options: - type: object - additionalProperties: true - description: Opaque WebAuthn `PublicKeyCredentialCreationOptions`. Pass to the device's WebAuthn registration API unmodified. - allowedOrigins: + description: Platform-specific customer identifier. If not provided, one will be generated by the system. + example: 9f84e0c2a72c4fa + customerType: + $ref: '#/components/schemas/CustomerType' + region: + type: string + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + example: US + currencies: type: array - description: The origins the WebAuthn registration ceremony may run against. The origin the credential is produced against must be one of these and must be echoed back on the confirm call. items: type: string + description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. example: - - https://app.example.com - relyingPartyId: + - USD + - USDC + email: type: string - description: The WebAuthn relying-party id the credential is bound to. - example: app.example.com - ScaFactorEnrollStartOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentStart' - - $ref: '#/components/schemas/PasskeyEnrollmentStart' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentStart' - PASSKEY: '#/components/schemas/PasskeyEnrollmentStart' - description: 'The factor-specific material needed to complete enrollment, keyed by `type`: a TOTP shared secret + provisioning URI, or the WebAuthn registration options for a passkey.' - TotpEnrollmentConfirmRequest: + format: email + description: Email address for the customer. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerCreateRequest: + title: Individual Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + - type: object + properties: + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + writeOnly: true + description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. + example: 123-45-6789 + BusinessInfo: type: object - description: The shared secret returned by the TOTP enrollment start, plus the first code the customer's authenticator app produces, submitted to confirm and finalize the TOTP factor. + description: Additional information required for business entities required: - - type - - secret - - code + - legalName + - taxId + - incorporatedOn properties: - type: + legalName: type: string - enum: - - TOTP - description: Discriminator selecting the TOTP confirm variant. - secret: + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: type: string - description: The shared secret returned as `secret` by the TOTP enrollment start, threaded back to bind the confirmation to that enrollment. - code: + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: type: string - description: The current time-based one-time code from the customer's authenticator app. In sandbox, the code is always `123456`. - example: '123456' - PasskeyEnrollmentConfirmRequest: - type: object - description: The WebAuthn credential a device produced for a passkey registration challenge, submitted to enroll the passkey factor. - required: - - type - - origin - - credential - properties: - type: + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: type: string - enum: - - PASSKEY - description: Discriminator selecting the passkey confirm variant. - origin: + description: Business registration number + example: '5523041' + incorporatedOn: type: string - description: The WebAuthn origin the `credential` was produced against (one of the enrollment start's `allowedOrigins`). - example: https://app.example.com - credential: - type: object - additionalProperties: true - description: Opaque WebAuthn credential the device produced from the enrollment start's `options`. - ScaFactorConfirmRequestOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentConfirmRequest' - - $ref: '#/components/schemas/PasskeyEnrollmentConfirmRequest' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentConfirmRequest' - PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmRequest' - description: 'The proof that finalizes enrollment, keyed by `type`: the TOTP shared secret + code, or the passkey `origin` + `credential`.' - TotpEnrollmentConfirmResponse: - type: object - description: The one-time recovery codes issued once a TOTP factor is enrolled. These are shown to the customer only once; store them somewhere safe to recover access if the authenticator device is lost. - required: - - type - - recoveryCodes - properties: - type: + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: type: string - enum: - - TOTP - description: Discriminator identifying this as the TOTP enrollment result. - recoveryCodes: + description: Tax identification number + example: 47-1234567 + countriesOfOperation: type: array - description: The one-time recovery codes for this TOTP factor. items: type: string + description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - ABCD-EFGH-IJKL - - MNOP-QRST-UVWX - PasskeyEnrollmentConfirmResponse: - type: object - description: The enrolled passkey factor returned after a successful confirmation. - required: - - type - - factor - properties: - type: + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: type: string enum: - - PASSKEY - description: Discriminator identifying this as the passkey enrollment result. - factor: - $ref: '#/components/schemas/ScaFactorView' - ScaFactorConfirmResponseOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentConfirmResponse' - - $ref: '#/components/schemas/PasskeyEnrollmentConfirmResponse' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentConfirmResponse' - PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmResponse' - description: The enrollment result, keyed by `type`. - ScaLoginStartRequest: - type: object - description: Selects which enrolled factor to start an SCA login with. The factor must already be enrolled (or, for `SMS_OTP`, the phone verified). - required: - - factor - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor to authenticate with. - ScaLoginStart: - type: object - description: 'The factor-specific material a customer needs to complete an SCA login. Each factor surfaces only the fields it issues: `SMS_OTP` carries `challengeId` and `expiresAt`; `TOTP` carries neither (the customer reads the code from their authenticator app); `PASSKEY` carries the opaque WebAuthn `passkeyOptions` with `allowedOrigins` and `relyingPartyId`.' - required: - - factor - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor this login was started for. - challengeId: - type: - - string - - 'null' - description: The challenge handle for an `SMS_OTP` login, threaded back on the complete call. Present only for `SMS_OTP`. - expiresAt: - type: - - string - - 'null' - format: date-time - description: Absolute UTC timestamp after which the `SMS_OTP` code expires. Present only for `SMS_OTP`. - example: '2025-10-03T12:05:00Z' - passkeyOptions: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion request options. Present only for `PASSKEY`; pass to the device's WebAuthn API to produce the assertion submitted on the complete call. - allowedOrigins: - type: - - array - - 'null' + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array items: type: string - description: The origins the WebAuthn ceremony may run against. Present only for `PASSKEY`. + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) example: - - https://app.example.com - relyingPartyId: - type: - - string - - 'null' - description: The WebAuthn relying-party id. Present only for `PASSKEY`. - example: app.example.com - ScaLoginCompleteRequest: - type: object - description: Completes an SCA login by submitting the proof for the started factor. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`), plus the `factor` being completed, the `endUserIpAddress` the login is being performed from, and, for `SMS_OTP`, the `challengeId` returned by the login start. - required: - - factor - - endUserIpAddress - anyOf: - - required: - - code - - required: - - passkeyAssertion - - origin - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor being completed; must match the started login. - endUserIpAddress: - type: string - description: The IP address of the end user's device completing this login, recorded against the login event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and any transaction-risk exemption. - example: 203.0.113.42 - challengeId: - type: - - string - - 'null' - description: The challenge handle returned by the login start, required for `SMS_OTP` and omitted for other factors. - code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. - example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the login start's `passkeyOptions`. Required when completing a `PASSKEY` login. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against (one of the login start's `allowedOrigins`). Required alongside `passkeyAssertion`; omit it for the `code` path. - example: https://app.example.com - ScaLoginComplete: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BusinessCustomerCreateRequest: + title: Business Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfo' + CustomerCreateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' + Error409: type: object - description: The status of a completed SCA login session. required: + - message - status + - code properties: status: + type: integer + enum: + - 409 + description: HTTP status code + code: type: string - description: The status of the login session. A successful login reports `SUCCESS`; other values indicate the login did not complete and should be surfaced to the caller. - example: SUCCESS - sessionExpiresAt: - type: - - string - - 'null' - format: date-time - description: Absolute UTC timestamp after which the customer's SCA session is no longer valid and they must complete another SCA login. Money movement in SCA-regulated currencies is refused once it passes, so prompt a re-login ahead of it rather than waiting for a `SCA_SESSION_REQUIRED` failure. Present when the login established a session. - example: '2026-01-29T12:00:00Z' - Error423: + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | + | UMA_ADDRESS_EXISTS | UMA address already exists | + | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | + | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | + | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | + enum: + - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL + - UMA_ADDRESS_EXISTS + - EMAIL_OTP_EMAIL_ALREADY_EXISTS + - EMAIL_OTP_CREDENTIAL_SET_CHANGED + - CONFLICT + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error404: type: object required: - message @@ -13214,117 +10752,142 @@ components: status: type: integer enum: - - 423 + - 404 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | ACCOUNT_LOCKED | The customer's login is temporarily locked (or suspended) after too many failed attempts. Inspect `details.lockedUntil` for when the customer may retry, and `details.failedAttempts` for the current count. | + | TRANSACTION_NOT_FOUND | Transaction not found | + | INVITATION_NOT_FOUND | Invitation not found | + | USER_NOT_FOUND | Customer not found | + | QUOTE_NOT_FOUND | Quote not found | + | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | + | TOKEN_NOT_FOUND | Token not found | + | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | + | REFERENCE_NOT_FOUND | Reference not found | + | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | + | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | enum: - - ACCOUNT_LOCKED + - TRANSACTION_NOT_FOUND + - INVITATION_NOT_FOUND + - USER_NOT_FOUND + - QUOTE_NOT_FOUND + - LOOKUP_REQUEST_NOT_FOUND + - TOKEN_NOT_FOUND + - BULK_UPLOAD_JOB_NOT_FOUND + - REFERENCE_NOT_FOUND + - UMA_NOT_FOUND + - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND message: type: string description: Error message details: type: object - description: Lockout detail. `lockedUntil` is the UTC timestamp the customer may retry after (absent when suspended indefinitely); `failedAttempts` is the current cumulative failed-login count; `suspended` is true when the account is locked with no automatic expiry. - properties: - suspended: - type: boolean - lockedUntil: - type: - - string - - 'null' - format: date-time - failedAttempts: - type: integer + description: Additional error details additionalProperties: true - RecordSecurityEventRequest: + Error410: type: object - description: Records a client-side security event for the customer so Grid can maintain the customer's login-security state (SCA session revocation and failed-login lockout). required: - - eventType + - message + - status + - code properties: - eventType: - type: string + status: + type: integer enum: - - RESET_PASSWORD_COMPLETED - - FAILED_LOGIN_ATTEMPT - description: | - The security event to record: - - | Value | Effect | - |-------|--------| - | `RESET_PASSWORD_COMPLETED` | Revokes every active SCA session for the customer and clears the failed-login counter. | - | `FAILED_LOGIN_ATTEMPT` | Increments the cumulative failed-login counter and escalates the lockout at each milestone: 5 attempts → 15 minutes, 6 → 30 minutes, 7 → 1 hour, 8 → 24 hours, 9 or more → suspension. | - example: FAILED_LOGIN_ATTEMPT - RecordSecurityEventResponse: - type: object - description: The customer's login-security state after recording the event, so the integrator can surface a lockout to the end user. - required: - - eventType - - suspended - - failedAttempts - properties: - eventType: + - 410 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | enum: - - RESET_PASSWORD_COMPLETED - - FAILED_LOGIN_ATTEMPT - description: The event that was recorded. - example: FAILED_LOGIN_ATTEMPT - suspended: - type: boolean - description: Whether the customer's login is currently suspended (locked with no automatic expiry). A suspended customer must go through a password reset (`RESET_PASSWORD_COMPLETED`) to clear the lockout. - lockedUntil: - type: - - string - - 'null' - format: date-time - description: When the customer may attempt to log in again, if temporarily locked. Null when not locked, or when `suspended` is true (no automatic expiry). - example: '2025-10-03T12:15:00Z' - failedAttempts: - type: integer - description: The customer's current cumulative failed-login count. - example: 5 - TwoFactorResetStartRequest: + - CUSTOMER_DELETED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerUpdateRequest: + title: Customer Update Request + description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. type: object - description: Selects which enrolled factor to reset via the liveness-gated recovery flow. required: - - factor + - customerType properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The enrolled factor to reset. - TwoFactorResetStart: + customerType: + $ref: '#/components/schemas/CustomerType' + currencies: + type: array + items: + type: string + description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + example: + - USD + - EUR + - USDC + email: + type: string + format: email + description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerUpdateRequest: + title: Individual Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + BusinessCustomerUpdateRequest: + title: Business Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + CustomerUpdateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + SignedRequestChallenge: + title: Signed Request Challenge type: object - description: The reset handle plus the opaque liveness handles a caller relays to the end-user device to complete the liveness check. `resetId` threads the ceremony together (status and complete reference it). `livenessAccessToken` and `verificationLink` are omitted when they are not returned. required: - - resetId + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. properties: - resetId: + payloadToSign: type: string - description: Identifier for this reset; pass it to the status and complete endpoints. - livenessAccessToken: - type: - - string - - 'null' - description: Access token for the embedded liveness/verification SDK, bound to this reset. Omitted when one is not returned. - verificationLink: - type: - - string - - 'null' - description: Hosted identity-verification page URL for completing liveness. Omitted when one is not returned. + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' + requestId: + type: string + description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 expiresAt: - type: - - string - - 'null' + type: string format: date-time - description: Absolute UTC timestamp at the end of the reset window. Omitted when one is not returned. - example: '2025-10-03T12:30:00Z' - Error429: + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' + Error424: type: object required: - message @@ -13334,16 +10897,24 @@ components: status: type: integer enum: - - 429 + - 424 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | + | PAYREQ_REQUEST_FAILED | Payment request failed | + | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | + | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | + | LNURLP_REQUEST_FAILED | LNURLP request failed | + | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | enum: - - RATE_LIMITED + - PAYREQ_REQUEST_FAILED + - COUNTERPARTY_PUBKEY_FETCH_ERROR + - NO_COMPATIBLE_UMA_VERSION + - LNURLP_REQUEST_FAILED + - EMAIL_OTP_CREDENTIAL_SYNC_FAILED message: type: string description: Error message @@ -13351,175 +10922,54 @@ components: type: object description: Additional error details additionalProperties: true - TwoFactorResetStatus: + KycLinkCreateRequest: type: object - description: The status of an in-progress 2FA reset, polled until it reaches a terminal value. - required: - - status - - factor - - expiresAt + description: Request body for generating a hosted KYC link for an existing customer. properties: - status: - type: string - enum: - - INITIATED - - PENDING_REVIEW - - LIVENESS_PASSED - - COMPLETED - - REJECTED - - EXPIRED - description: |- - The reset status. - | Value | Terminal | Meaning | |-------|----------|---------| | `INITIATED` | no | Reset started; liveness not yet submitted. Keep polling. | | `PENDING_REVIEW` | no | Liveness submitted; under review. Keep polling. | | `LIVENESS_PASSED` | no | Liveness passed; call the complete endpoint to finish the reset. | | `COMPLETED` | **yes** | Reset finished; the lost factor is cleared and re-enrollment can begin. | | `REJECTED` | **yes** | Liveness failed. Stop polling; start a new reset. | | `EXPIRED` | **yes** | The reset window closed before it completed. Stop polling; start a new reset. | - Stop polling on any terminal value. - example: INITIATED - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor being reset. - enrollmentStatus: - type: - - string - - 'null' - enum: - - PENDING - - COMPLETED - - null - description: The enrollment status of the replacement factor. `PENDING` until the customer finishes re-enrolling, then `COMPLETED`. Null for an `SMS_OTP` reset, where completing the reset enrolls the new number directly with no separate enrollment step. - example: PENDING - expiresAt: + redirectUri: type: string - format: date-time - description: When the reset window closes. Poll no longer than this. - example: '2025-10-03T12:15:00Z' - completedAt: - type: - - string - - 'null' - format: date-time - description: When the reset completed; null until then. - example: null - TwoFactorResetCompleteRequest: - type: object - description: Optional body for completing a 2FA reset. Only needed when resetting the `SMS_OTP` factor to a new phone number; omit the body entirely otherwise. - properties: - mobile: - type: object - description: For an `SMS_OTP` reset, the new mobile number to enroll as the customer completes the reset. Required for an `SMS_OTP` reset; ignored for `TOTP` / `PASSKEY` resets. - required: - - countryCode - - number - properties: - countryCode: - type: string - description: The country dialing code, including the leading `+`. - example: '+1' - number: - type: string - description: The national subscriber number, without the country code. - example: '4155550123' - ScaChallenge: + format: uri + description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. + example: https://app.example.com/onboarding/completed + KycProvider: + type: string + description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. + enum: + - SUMSUB + example: SUMSUB + KycLinkResponse: type: object - description: |- - A Strong Customer Authentication challenge that must be satisfied before a money-movement operation can complete. This object is **only present when the customer is in a region where SCA is required** (the EU); for customers outside SCA-regulated regions it is omitted entirely and no action is needed. - - When present on a quote, authorize it by submitting an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize`. - - **A single operation may require more than one authorization, in sequence.** Treat `scaChallenge` as *the challenge to satisfy now*, not "the only one". After each authorize, re-inspect the returned quote/transaction: if it is still `PENDING_AUTHORIZATION`, it carries the **next** `scaChallenge` (a new `id`) — authorize that too, and repeat until it leaves `PENDING_AUTHORIZATION`. Do not assume one authorization releases the transfer. The number of authorizations is flow-dependent and **may decrease in future**: for example, a cross-currency send today authorizes the currency conversion and the payout as two separate challenges; a future update may collapse them into one. A client written to loop on status handles any count unchanged. + description: A hosted KYC link that the customer can complete to verify their identity. required: - - id + - kycUrl - expiresAt - - factor - - availableFactors + - provider properties: - id: + kycUrl: type: string - description: Unique identifier for this challenge. The server resolves the active challenge from the quote or transaction being authorized, so this field need not be supplied back; it is informational (e.g. for logging or correlation). - example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007 + description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. + example: https://kyc.lightspark.com/onboard/abc123def456 expiresAt: type: string format: date-time - description: Absolute UTC timestamp after which this challenge can no longer be authorized. - example: '2025-10-03T12:05:00Z' - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor this challenge was issued for. Defaults to `SMS_OTP`. - availableFactors: - type: array - description: The factors the customer may use to satisfy this challenge. - items: - $ref: '#/components/schemas/ScaFactor' - example: - - SMS_OTP - purpose: - type: - - string - - 'null' - description: Optional, informational label for what this particular challenge in the sequence authorizes — useful for step UX (e.g. "Authorize the currency conversion" vs "Authorize the payout"). Known values include `CURRENCY_CONVERSION`, `PAYOUT`, and `TRANSFER`, but the set is **non-exhaustive and may grow** — treat unrecognized values as a generic authorization step and do not branch program logic on it. Omitted when steps are not distinguished (e.g. a single-authorization flow). - example: PAYOUT - passkeyAssertionOptions: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion request options (including the relying-party id, challenge, and allowed credentials), present only when `factor` is `PASSKEY`. Pass to the device's WebAuthn API to produce the assertion submitted back in `ScaAuthorization.passkeyAssertion`. - passkeyAllowedOrigins: - type: - - array - - 'null' - items: - type: string - description: The origins the WebAuthn ceremony may run against. Populated for enrollment and login passkey challenges; the origin the assertion is produced against must be one of these and echoed back as `ScaAuthorization.origin`. Per-transaction passkey challenges omit this (they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin` for how to source the origin in that case. - example: - - https://app.example.com - BeneficiaryTrustStart: - type: object - description: The SCA challenge (if any) a caller authorizes to finish trusting (or untrusting) a beneficiary. The beneficiary is identified by its `externalAccountId`, so the confirm call needs no separate handle. `scaChallenge` is omitted when no challenge is issued; the caller then confirms without a `challengeId`. - properties: - scaChallenge: - $ref: '#/components/schemas/ScaChallenge' - description: The SCA challenge to satisfy on the confirm call. Omitted when no challenge is issued. - BeneficiaryTrustConfirmRequest: + description: Time at which the hosted link expires and can no longer be used. + example: '2027-01-15T14:32:00Z' + provider: + $ref: '#/components/schemas/KycProvider' + token: + type: string + description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. + example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ContactVerificationConfirmRequest: type: object - description: Confirms trusting or untrusting a beneficiary by submitting the SCA proof. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`) and, when the start issued one, the `challengeId`. The beneficiary is identified by the `externalAccountId` in the path — no separate handle is needed. - anyOf: - - required: - - code - - required: - - passkeyAssertion - - origin + description: Request body for confirming an email or phone verification challenge. + required: + - code properties: - challengeId: - type: - - string - - 'null' - description: The challenge handle from the trust start's `scaChallenge`, when one was issued. Omit when the start returned no challenge. code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. + type: string + description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the challenge's assertion options. Required when satisfying a `PASSKEY` challenge. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against. Required alongside `passkeyAssertion`; omit it for the `code` path. - example: https://app.example.com - BeneficiaryTrustConfirm: - type: object - description: The result of a confirm-trust / confirm-untrust call. `trusted` is `true` after a successful trust and `false` after a successful untrust. - required: - - trusted - properties: - trusted: - type: boolean - description: Whether the beneficiary is now trusted. `true` after a successful trust, `false` after a successful untrust. InternalAccountType: title: Internal Account Type type: string @@ -13551,6 +11001,19 @@ components: - `FROZEN`: The account cannot send or receive payments. Grid may freeze an account in response to compliance or fraud signals; payments are blocked while the account remains frozen. - `FAILED`: The account could not be provisioned. Grid was unable to create the underlying account, so it cannot send or receive payments and requires remediation. example: ACTIVE + CurrencyAmount: + type: object + required: + - amount + - currency + properties: + amount: + type: integer + format: int64 + description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) + example: 12550 + currency: + $ref: '#/components/schemas/Currency' PaymentAccountType: type: string enum: @@ -13576,12 +11039,10 @@ components: - LIGHTNING - SOLANA_WALLET - TRON_WALLET - - PLASMA_WALLET - POLYGON_WALLET - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET - - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -13614,43 +11075,10 @@ components: minLength: 9 maxLength: 9 pattern: ^[0-9]{9}$ - bankName: - type: string - description: The name of the financial institution holding the account. Optional on every rail, and recommended for wires, where it identifies the beneficiary's institution on the payment message. - example: Chase Bank - minLength: 1 - maxLength: 140 - bankAccountType: - type: string - description: Whether the account is a checking or a savings account. Optional on every rail; when omitted, the account is treated as a checking account. - enum: - - CHECKING - - SAVINGS - example: CHECKING - intermediaryBankName: - type: string - description: The name of the intermediary financial institution, for accounts reachable only through a correspondent bank. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: JPMorgan Chase Bank - minLength: 1 - maxLength: 140 - intermediaryRoutingNumber: - type: string - description: The ABA routing number of the intermediary financial institution. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: '021000021' - minLength: 9 - maxLength: 9 - pattern: ^[0-9]{9}$ - fiToFiInformation: - type: string - description: Bank-to-bank instructions carried alongside the payment. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: /BNF/Invoice 4471 - maxLength: 210 example: accountType: USD_ACCOUNT accountNumber: '1234567890' routingNumber: '021000021' - bankName: Chase Bank - bankAccountType: CHECKING UsdAccountInfo: allOf: - $ref: '#/components/schemas/UsdAccountInfoBase' @@ -14623,32 +12051,6 @@ components: description: Type of asset enum: - USDT - PlasmaWalletInfo: - type: object - required: - - address - - accountType - properties: - accountType: - type: string - enum: - - PLASMA_WALLET - address: - type: string - description: Plasma wallet address - example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - PaymentPlasmaWalletInfo: - title: Plasma Wallet - allOf: - - $ref: '#/components/schemas/BasePaymentAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletInfo' - - type: object - properties: - assetType: - type: string - description: Type of asset - enum: - - USDT PolygonWalletInfo: type: object required: @@ -14727,29 +12129,6 @@ components: description: Type of asset enum: - USDC - - USDT - PaymentBitcoinDepositAddressInfo: - title: Bitcoin L1 Deposit Address - allOf: - - $ref: '#/components/schemas/BasePaymentAccountInfo' - - type: object - required: - - address - properties: - accountType: - type: string - enum: - - BITCOIN_L1 - address: - type: string - description: On-chain Bitcoin (L1) deposit address to send funds to - example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq - network: - type: string - description: The blockchain network for the deposit address. - enum: - - BITCOIN - example: BITCOIN AedAccountInfoBase: type: object required: @@ -16127,11 +13506,9 @@ components: - $ref: '#/components/schemas/PaymentLightningInvoiceInfo' - $ref: '#/components/schemas/PaymentSolanaWalletInfo' - $ref: '#/components/schemas/PaymentTronWalletInfo' - - $ref: '#/components/schemas/PaymentPlasmaWalletInfo' - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' - - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -16156,11 +13533,9 @@ components: LIGHTNING: '#/components/schemas/PaymentLightningInvoiceInfo' SOLANA_WALLET: '#/components/schemas/PaymentSolanaWalletInfo' TRON_WALLET: '#/components/schemas/PaymentTronWalletInfo' - PLASMA_WALLET: '#/components/schemas/PaymentPlasmaWalletInfo' POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' - BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -16342,7 +13717,6 @@ components: - BASE_WALLET - ETHEREUM_WALLET - LIGHTNING - - PLASMA_WALLET - POLYGON_WALLET - SOLANA_WALLET - SPARK_WALLET @@ -18448,11 +15822,6 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletInfo' - PlasmaWalletExternalAccountInfo: - title: Plasma Wallet - allOf: - - $ref: '#/components/schemas/BaseExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletInfo' SolanaWalletExternalAccountInfo: title: Solana Wallet allOf: @@ -18562,7 +15931,6 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -18612,7 +15980,6 @@ components: LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' LIGHTNING_ACCOUNT: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' - PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -19482,7 +16849,6 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -19531,7 +16897,6 @@ components: ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo' LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' - PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -20024,7 +17389,6 @@ components: enum: - CREATED - PENDING - - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - REJECTED @@ -20038,7 +17402,6 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -20078,7 +17441,6 @@ components: - BASE - POLYGON - TRON - - PLASMA - SPARK description: The blockchain network an on-chain transaction settled on. Whether this is the mainnet or a test network (e.g. Solana devnet) is determined by your platform's environment — sandbox platforms operate on test networks, production platforms on mainnet — mirroring how `cryptoNetwork` is interpreted elsewhere in the API. example: SOLANA @@ -20143,43 +17505,6 @@ components: mapping: ACCOUNT: '#/components/schemas/AccountTransactionDestination' UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionDestination' - Refund: - type: object - required: - - reference - - initiatedAt - - status - properties: - reference: - type: string - description: The unique reference ID of the refund - example: UMA-Q12345-REFUND - initiatedAt: - type: string - format: date-time - description: When the refund was initiated - example: '2025-08-15T14:30:00Z' - settledAt: - type: string - format: date-time - description: When the refund was settled - example: '2025-08-15T14:35:00Z' - status: - type: string - enum: - - PENDING - - COMPLETED - - FAILED - description: Current status of the refund - example: COMPLETED - reason: - type: string - enum: - - TRANSACTION_FAILED - - USER_CANCELLATION - - TIMEOUT - description: Reason for the refund - example: TRANSACTION_FAILED CounterpartyInformation: type: object description: Additional information about the counterparty, if available and relevant to the transaction and platform. @@ -20214,11 +17539,11 @@ components: $ref: '#/components/schemas/TransactionDestinationOneOf' customerId: type: string - description: System ID of the customer this transaction belongs to + description: System ID of the customer (sender for outgoing, recipient for incoming) example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific ID of the customer this transaction belongs to + description: Platform-specific ID of the customer (sender for outgoing, recipient for incoming) example: 18d3e5f7b4a9c2 settledAt: type: string @@ -20248,21 +17573,6 @@ components: type: string description: Optional memo or description for the payment example: 'Payment for invoice #1234' - sentAmount: - $ref: '#/components/schemas/CurrencyAmount' - description: Amount sent in the sender's currency - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 1.08 - quoteId: - type: string - description: The ID of the quote that was used to trigger this payment - example: Quote:019542f5-b3e7-1d02-0000-000000000006 - refund: - $ref: '#/components/schemas/Refund' - description: The refund if transaction was refunded. counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' TransactionSourceType: @@ -20404,6 +17714,39 @@ components: type: string description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' + IncomingRateDetails: + description: 'Details about the rate and fees for an incoming transaction. Note: `gridApiFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' + type: object + required: + - gridApiMultiplier + - gridApiFixedFee + - gridApiVariableFeeRate + - gridApiVariableFeeAmount + properties: + gridApiMultiplier: + type: number + format: double + description: The underlying multiplier from the mSATS to the receiving currency, including variable fees. + exclusiveMinimum: 0 + example: 0.925 + gridApiFixedFee: + type: integer + format: int64 + description: The fixed fee charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). + minimum: 0 + example: 10 + gridApiVariableFeeRate: + type: number + format: double + description: The variable fee rate charged by the Grid product to execute the quote as a percentage of the receiving currency amount. + minimum: 0 + example: 0.003 + gridApiVariableFeeAmount: + type: number + format: int64 + description: The variable fee amount charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). This is the receiving amount times gridApiVariableFeeRate. + minimum: 0 + example: 30 IncomingTransactionFailureReason: type: string enum: @@ -20437,12 +17780,15 @@ components: fees: type: integer format: int64 - description: The total fees available from the receive quote in the smallest unit of the sending currency (eg. cents). + description: The total fees available from the receive quote in the smallest unit of the receiving currency (eg. cents). minimum: 0 example: 10 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Included for all transactions except those with "CREATED" status + rateDetails: + $ref: '#/components/schemas/IncomingRateDetails' + description: Details about the rate and fees for the transaction. failureReason: $ref: '#/components/schemas/IncomingTransactionFailureReason' description: If the transaction failed, this field provides the reason for failure. @@ -20450,7 +17796,6 @@ components: type: string enum: - PENDING - - PENDING_AUTHORIZATION - EXPIRED - PROCESSING - COMPLETED @@ -20461,11 +17806,47 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | | `FAILED` | Something went wrong — accompanied by a `failureReason` | + Refund: + type: object + required: + - reference + - initiatedAt + - status + properties: + reference: + type: string + description: The unique reference ID of the refund + example: UMA-Q12345-REFUND + initiatedAt: + type: string + format: date-time + description: When the refund was initiated + example: '2025-08-15T14:30:00Z' + settledAt: + type: string + format: date-time + description: When the refund was settled + example: '2025-08-15T14:35:00Z' + status: + type: string + enum: + - PENDING + - COMPLETED + - FAILED + description: Current status of the refund + example: COMPLETED + reason: + type: string + enum: + - TRANSACTION_FAILED + - USER_CANCELLATION + - TIMEOUT + description: Reason for the refund + example: TRANSACTION_FAILED OutgoingRateDetails: description: 'Details about the rate and fees for an outgoing transaction or quote. Note: `counterpartyFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' type: object @@ -20521,15 +17902,7 @@ components: - LIGHTNING_PAYMENT_FAILED - FUNDING_AMOUNT_MISMATCH - COUNTERPARTY_POST_TX_FAILED - - SCA_NOT_COMPLETED - - EXECUTION_FAILED_POST_DEBIT - - SETTLEMENT_FAILED - - TIMEOUT - - MANUAL_REFUND - description: |- - Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. - - `SCA_NOT_COMPLETED` means the customer did not satisfy the Strong Customer Authentication challenge before it expired, so the transaction was never authorized and no funds were moved. Only occurs for customers in a region where SCA is required (e.g. the EU). Create a new quote to try again, and have the customer authorize it while the challenge is live. + description: Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. RailSelectionMode: type: string enum: @@ -20561,21 +17934,24 @@ components: receivedAmount: $ref: '#/components/schemas/CurrencyAmount' description: Amount to be received by recipient in the recipient's currency + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 1.08 fees: type: integer format: int64 description: The fees associated with the quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 - platformFees: - type: integer - format: int64 - description: The portion of `fees` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `fees`. - minimum: 0 - example: 5 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Reconciliation details for this transaction. For the on-chain hash of a crypto payout to an external wallet, see the destination's `onChainTransaction` instead. + quoteId: + type: string + description: The ID of the quote that was used to trigger this payment + example: Quote:019542f5-b3e7-1d02-0000-000000000006 paymentInstructions: type: array description: Payment instructions for executing the payment. @@ -20597,6 +17973,9 @@ components: assetType: BTC address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + refund: + $ref: '#/components/schemas/Refund' + description: The refund if transaction was refunded. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. @@ -20823,24 +18202,6 @@ components: description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. allOf: - $ref: '#/components/schemas/PaymentRail' - PurposeOfPayment: - type: string - description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). - enum: - - GIFT - - SELF - - GOODS_OR_SERVICES - - EDUCATION - - HEALTH_OR_MEDICAL - - REAL_ESTATE_PURCHASE - - TAX_PAYMENT - - LOAN_PAYMENT - - UTILITY_BILL - - DONATION - - TRAVEL - - FAMILY_SUPPORT - - SALARY_PAYMENT - - OTHER TransferOutRequest: type: object required: @@ -20863,8 +18224,6 @@ components: maxLength: 80 description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' example: '12345' - purposeOfPayment: - $ref: '#/components/schemas/PurposeOfPayment' CurrencyPreference: type: object required: @@ -21124,12 +18483,11 @@ components: type: string enum: - PENDING - - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - FAILED - EXPIRED - description: 'Current status of the quote. `PENDING_AUTHORIZATION` occurs only for customers in a region where Strong Customer Authentication is required (e.g. EU): the quote carries an `scaChallenge` that must be authorized before execution, and for realtime-funding sources `paymentInstructions` are withheld until it is satisfied.' + description: Current status of the quote example: PENDING createdAt: type: string @@ -21173,12 +18531,6 @@ components: description: 'The fees associated with the quote in the smallest unit of the sending currency (eg. cents). Note: this value may fluctuate between quotes — some underlying fee components are defined in the receiving currency, so their equivalent in the sending currency moves with the FX rate. The fees shown here are locked only for the lifetime of this quote.' minimum: 0 example: 10 - platformFeesIncluded: - type: integer - format: int64 - description: The portion of `feesIncluded` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `feesIncluded`. May be omitted from payloads produced before platform fees existed. - minimum: 0 - example: 5 paymentInstructions: type: array description: Payment instructions for executing the payment. This is not required when using an internal account source. @@ -21204,22 +18556,41 @@ components: type: string description: The ID of the transaction created from this quote. example: Transaction:019542f5-b3e7-1d02-0000-000000000005 + remittanceInformation: + type: string + maxLength: 80 + description: 'Free-form information about the payment that travels with it to the recipient, as provided on the quote request. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' + example: '12345' counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' description: Additional information about the counterparty, if available and required by the platform in their configuration. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. - scaChallenge: - $ref: '#/components/schemas/ScaChallenge' - readOnly: true - description: 'Present only while `status` is `PENDING_AUTHORIZATION`: the Strong Customer Authentication challenge to satisfy before this quote can be executed (or, for realtime-funding sources, before `paymentInstructions` are issued). Omitted for customers outside SCA-regulated regions (non-EU).' QuoteLockSide: type: string enum: - SENDING - RECEIVING description: The side of the quote which should be locked and specified in the `lockedCurrencyAmount`. For example, if I want to send exactly $5 MXN from my wallet, I would set this to "sending", and the `lockedCurrencyAmount` to 500 (in cents). If I want the receiver to receive exactly $10 USD, I would set this to "receiving" and the `lockedCurrencyAmount` to 10000 (in cents). + PurposeOfPayment: + type: string + description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). + enum: + - GIFT + - SELF + - GOODS_OR_SERVICES + - EDUCATION + - HEALTH_OR_MEDICAL + - REAL_ESTATE_PURCHASE + - TAX_PAYMENT + - LOAN_PAYMENT + - UTILITY_BILL + - DONATION + - TRAVEL + - FAMILY_SUPPORT + - SALARY_PAYMENT + - OTHER QuoteRequest: type: object required: @@ -21265,9 +18636,6 @@ components: example: '12345' purposeOfPayment: $ref: '#/components/schemas/PurposeOfPayment' - scaFactor: - $ref: '#/components/schemas/ScaFactor' - description: Optional preferred factor for a Strong Customer Authentication challenge issued at quote creation. Only relevant for a realtime-funding source in a region where SCA is required (e.g. EU); ignored otherwise. Valid values are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected. When the quote is returned in `PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`. senderCustomerInfo: type: object additionalProperties: true @@ -21277,41 +18645,6 @@ components: example: FULL_NAME: Jane Receiver NATIONALITY: FR - ExecuteQuoteRequest: - type: object - description: Optional body for executing a quote. Only needed to request a specific Strong Customer Authentication factor (`scaFactor`) for the challenge this call issues; omit the body entirely otherwise. - properties: - scaFactor: - $ref: '#/components/schemas/ScaFactor' - description: Optional preferred factor for the Strong Customer Authentication challenge this call issues. Only relevant for customers in a region where SCA is required (e.g. EU); ignored otherwise. Valid values for a per-transaction challenge are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected here. Omit to default to `SMS_OTP`. - ScaAuthorization: - type: object - description: Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for `SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a `passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound to the origin it was produced against, and a passkey confirmation is rejected without it. - properties: - code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. In sandbox, the code is always `123456`. - example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the challenge's `passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against. **Required** alongside `passkeyAssertion`; omit it for the `code` path. When the challenge lists `passkeyAllowedOrigins` (enrollment / login challenges), it must be one of those. A per-transaction passkey challenge carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in that case supply the origin your app invoked the WebAuthn API from, which must match the relying party in `passkeyAssertionOptions`. - example: https://app.example.com - endUserIpAddress: - type: - - string - - 'null' - description: 'The IP address of the end user''s device authorizing this operation, forwarded to the SCA provider where it feeds risk assessment and any transaction-risk exemption. Supply the customer''s address, not your server''s. Optional: the provider records it for money-movement authorizations, and ignores it for beneficiary trust changes.' - example: 203.0.113.42 TransactionListResponse: type: object required: @@ -21354,13 +18687,6 @@ components: type: string description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes. example: RESTRICTED_JURISDICTION - CancelTransactionRequest: - type: object - properties: - reason: - type: string - description: Optional reason for cancelling the transaction. This is just for debugging purposes or can be used for a platform's own purposes. - example: REQUESTED_AFTER_HOURS EstimateCryptoWithdrawalFeeRequest: type: object required: @@ -22291,6 +19617,33 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the `encryptedSessionSigningKey` must not be used to sign further requests. example: '2026-04-09T15:30:01Z' + Error429: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 429 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | + enum: + - RATE_LIMITED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. @@ -23148,174 +20501,592 @@ components: description: Indicates if more results are available beyond this page nextCursor: type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of cards matching the criteria (excluding pagination) + CardCreateRequest: + type: object + required: + - cardholderId + - form + - fundingSources + properties: + cardholderId: + type: string + description: The id of the `Customer` to issue the card to. The customer must have KYC status `APPROVED`; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: + type: string + description: Optional platform-specific card identifier. System-generated when omitted, mirroring `platformCustomerId` semantics. + example: card-emp-aary-001 + form: + $ref: '#/components/schemas/CardForm' + fundingSources: + type: array + description: Internal account ids to bind as funding sources, in priority order. The first entry is tried first by Authorization Decisioning. Every card must be bound to at least one source, and every source must belong to the cardholder and be denominated in a card-eligible currency (USDB in v1); otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. + minItems: 1 + items: + type: string + example: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + CardUpdateRequest: + type: object + description: Update request for `PATCH /cards/{id}`. At least one of `state` or `fundingSources` must be supplied. `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`; any other transition returns `409 INVALID_STATE_TRANSITION`. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSources`. `fundingSources`, when supplied, fully replaces the card's bound funding sources — the array order determines the priority Authorization Decisioning tries them in. + properties: + state: + type: string + enum: + - ACTIVE + - FROZEN + - CLOSED + description: Target state for the card. Permitted transitions are `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. + example: FROZEN + fundingSources: + type: array + description: 'New ordered list of internal account ids to bind as funding sources. Fully replaces the previous binding. Each id must belong to the cardholder and be denominated in the card''s currency. The list must contain at least one source — to stop a card from spending without removing all sources, transition it to `FROZEN` instead. Cannot be supplied alongside `state: CLOSED`.' + minItems: 1 + items: + type: string + example: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + CardRevealResponse: + type: object + required: + - panEmbedUrl + - expiresAt + properties: + panEmbedUrl: + type: string + format: uri + description: 'Signed URL of the card processor''s iframe that securely displays the PAN, CVV, and expiry to the cardholder. The full PAN and CVV never cross Grid''s servers — render this URL in an iframe in your client to reveal card details. The URL is a short-lived bearer secret: render it immediately and never store, cache, or log it.' + example: https://embed.lithic.com/iframe/...?t=... + expiresAt: + type: string + format: date-time + description: When the signed URL stops loading. Request a new reveal rather than re-rendering an expired URL. + example: '2026-05-08T14:16:00Z' + SandboxCardAuthorizationRequest: + type: object + required: + - amount + - currency + - merchant + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the endpoint documentation for the suffix table. + properties: + amount: + type: integer + format: int64 + description: Authorization amount in the smallest unit of `currency` (e.g. cents for USD). + exclusiveMinimum: 0 + example: 1250 + currency: + $ref: '#/components/schemas/Currency' + merchant: + $ref: '#/components/schemas/CardMerchant' + SandboxCardClearingRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/clearing`. Drives a clearing event against an existing `CardTransaction`. Pass an `amount` greater than the authorized amount to exercise the over-auth / restaurant-tip post-hoc-pull path; pass `0` to exercise `AUTHORIZATION_EXPIRY`. Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to clear against. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: Clearing amount in the smallest unit of the transaction's currency. Set to `0` to simulate an authorization expiry with no clearing. + minimum: 0 + example: 1500 + SandboxCardReturnRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/return`. Drives a `RETURN` event against an existing settled `CardTransaction`, which creates a `CardRefund` and pushes the parent transaction towards `REFUNDED` (full) or keeps it `SETTLED` (partial). + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to refund against. Must have at least one settled clearing. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). + exclusiveMinimum: 0 + example: 1500 + StablecoinIssuanceStatus: + type: string + enum: + - PENDING_REVIEW + - PROVISIONING + - PROVISIONED + - REJECTED + - FAILED + description: Provider issuance/linking status for the registered stablecoin. + StablecoinGridOperationsStatus: + type: string + enum: + - NOT_ENABLED + - PENDING_APPROVAL + - ENABLING + - ENABLED + - DISABLED + description: Whether the stablecoin has been enabled for normal Grid operations. + StablecoinProvider: + type: string + enum: + - BRALE + description: Stablecoin provider backing the linked account, stablecoin, or operation. + StablecoinNetwork: + type: string + enum: + - SPARK + description: Public network family for the stablecoin token identifier. The concrete Spark network is inferred from the platform mode. + StablecoinProviderEnvironment: + type: string + enum: + - SANDBOX + - PRODUCTION + description: Provider environment derived from the authenticated Grid platform mode. + Stablecoin: + type: object + required: + - id + - name + - symbol + - baseCurrency + - network + - decimals + - issuanceStatus + - gridOperationsStatus + - networkTokenIdentifier + - provider + - stablecoinProviderAccountId + - providerEnvironment + - providerTokenIdentifier + - createdAt + - updatedAt + properties: + id: + type: string + description: System-generated stablecoin identifier. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + name: + type: string + description: Stablecoin display name. + example: Acme Dollar + symbol: + type: string + description: Stablecoin symbol. + example: ACME + baseCurrency: + type: string + description: Fiat currency the stablecoin is denominated against. + example: USD + network: + $ref: '#/components/schemas/StablecoinNetwork' + decimals: + type: integer + description: Number of decimal places used by the stablecoin. + example: 6 + issuanceStatus: + $ref: '#/components/schemas/StablecoinIssuanceStatus' + gridOperationsStatus: + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + networkTokenIdentifier: + type: string + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + currency: + type: string + description: Grid currency code after the stablecoin is enabled for Grid operations. + example: ACME + provider: + $ref: '#/components/schemas/StablecoinProvider' + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link selected for this stablecoin. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + providerEnvironment: + $ref: '#/components/schemas/StablecoinProviderEnvironment' + providerTokenIdentifier: + type: string + description: Provider token identifier. For Brale this maps to `value_type`. + example: ACME + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' + StablecoinListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + items: + $ref: '#/components/schemas/Stablecoin' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: + type: string + description: Cursor to retrieve the next page of results. totalCount: type: integer - description: Total number of cards matching the criteria (excluding pagination) - CardCreateRequest: + description: Total number of stablecoins matching the criteria. + StablecoinRegisterRequest: type: object required: - - cardholderId - - form - - fundingSources + - providerTokenIdentifier + - networkTokenIdentifier + - network properties: - cardholderId: + providerTokenIdentifier: type: string - description: The id of the `Customer` to issue the card to. The customer must have KYC status `APPROVED`; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: + description: Provider identifier for the stablecoin. For Brale this maps to `value_type`. + example: ACME + networkTokenIdentifier: type: string - description: Optional platform-specific card identifier. System-generated when omitted, mirroring `platformCustomerId` semantics. - example: card-emp-aary-001 - form: - $ref: '#/components/schemas/CardForm' - fundingSources: - type: array - description: Internal account ids to bind as funding sources, in priority order. The first entry is tried first by Authorization Decisioning. Every card must be bound to at least one source, and every source must belong to the cardholder and be denominated in a card-eligible currency (USDB in v1); otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. - minItems: 1 - items: - type: string - example: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - CardUpdateRequest: + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + network: + $ref: '#/components/schemas/StablecoinNetwork' + stablecoinProviderAccountId: + type: string + description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + StablecoinMintFundingSource: type: object - description: Update request for `PATCH /cards/{id}`. At least one of `state` or `fundingSources` must be supplied. `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`; any other transition returns `409 INVALID_STATE_TRANSITION`. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSources`. `fundingSources`, when supplied, fully replaces the card's bound funding sources — the array order determines the priority Authorization Decisioning tries them in. + required: + - type properties: - state: + type: type: string enum: - - ACTIVE - - FROZEN - - CLOSED - description: Target state for the card. Permitted transitions are `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. - example: FROZEN - fundingSources: - type: array - description: 'New ordered list of internal account ids to bind as funding sources. Fully replaces the previous binding. Each id must belong to the cardholder and be denominated in the card''s currency. The list must contain at least one source — to stop a card from spending without removing all sources, transition it to `FROZEN` instead. Cannot be supplied alongside `state: CLOSED`.' - minItems: 1 - items: - type: string - example: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - CardRevealResponse: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. + externalAccountId: + type: string + description: Grid `ExternalAccount` used as the ACH debit funding account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + accountId: + type: string + description: Grid internal funding account id for future Grid-funded mint flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type for future provider internal balance funding. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for future provider internal balance funding. + example: SOLANA + StablecoinMintDestination: type: object required: - - panEmbedUrl - - expiresAt + - type properties: - panEmbedUrl: + type: type: string - format: uri - description: 'Signed URL of the card processor''s iframe that securely displays the PAN, CVV, and expiry to the cardholder. The full PAN and CVV never cross Grid''s servers — render this URL in an iframe in your client to reveal card details. The URL is a short-lived bearer secret: render it immediately and never store, cache, or log it.' - example: https://embed.lithic.com/iframe/...?t=... - expiresAt: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. + externalAccountId: type: string - format: date-time - description: When the signed URL stops loading. Request a new reveal rather than re-rendering an expired URL. - example: '2026-05-08T14:16:00Z' - SandboxCardAuthorizationRequest: + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + accountId: + type: string + description: Grid internal receiving account id for future Grid-managed mint destinations. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + StablecoinMintRequest: type: object required: - amount - - currency - - merchant - description: 'Sandbox-only request body shared by the card authorization-family simulate endpoints: `simulate/authorization`, `simulate/credit_authorization`, `simulate/financial_authorization`, `simulate/financial_credit_authorization`, and `simulate/credit_authorization_advice`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` documentation for the suffix table.' + - fundingSource + - destination properties: amount: - type: integer - format: int64 - description: Authorization amount in the smallest unit of `currency` (e.g. cents for USD). - exclusiveMinimum: 0 - example: 1250 - currency: - $ref: '#/components/schemas/Currency' - merchant: - $ref: '#/components/schemas/CardMerchant' - SandboxCardSimulationResponse: + type: string + pattern: ^[0-9]+$ + description: Amount to mint in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + fundingSource: + $ref: '#/components/schemas/StablecoinMintFundingSource' + destination: + $ref: '#/components/schemas/StablecoinMintDestination' + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Initial program funding mint + StablecoinOperationType: + type: string + enum: + - MINT + - BURN + description: Stablecoin issuer operation type. + StablecoinOperationStatus: + type: string + enum: + - CREATED + - PENDING_FUNDING + - PENDING_PROVIDER + - PROCESSING + - COMPLETED + - FAILED + - EXPIRED + description: Stablecoin issuer operation lifecycle status. + StablecoinBurnSource: type: object required: - - issuerTransactionToken - description: Response body for the sandbox card-event simulators. The simulate call pokes the card issuer's sandbox; the resulting card operation is delivered asynchronously via the issuer's events webhook, never synchronously in this response. + - type properties: - issuerTransactionToken: + type: type: string - description: The card issuer's transaction token for the simulated event. Correlates the eventual webhook-delivered card operation back to this simulate call. - example: f3a1c2d4-5b6e-7890-abcd-ef0123456789 - SandboxCardClearingRequest: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + accountId: + type: string + description: Grid internal source account id for future Grid-controlled burn flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + StablecoinFundingInstructions: type: object + description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. + additionalProperties: true + example: + rail: SPARK + network: SPARK + address: spark1... + valueType: ACME + StablecoinOperationDestination: + type: object + description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint and burn destination request variants) so the field is unambiguously deserializable regardless of operation type: `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' required: - - cardTransactionId - - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/clearing`. Drives a clearing event against an existing `CardTransaction`. Pass an `amount` greater than the authorized amount to exercise the over-auth / restaurant-tip post-hoc-pull path; pass `0` to exercise `AUTHORIZATION_EXPIRY`. Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds. + - type properties: - cardTransactionId: + type: type: string - description: The id of the `CardTransaction` to clear against. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: - type: integer - format: int64 - description: Clearing amount in the smallest unit of the transaction's currency. Set to `0` to simulate an authorization expiry with no clearing. - minimum: 0 - example: 1500 - SandboxCardReturnRequest: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Destination variant. + externalAccountId: + type: string + description: Grid `ExternalAccount` id, when the destination is an external account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + accountId: + type: string + description: Grid internal account id, when the destination is a Grid-managed account. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail, present for burn (fiat redemption) destinations. + StablecoinEstimatedDelivery: + type: object + description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. + additionalProperties: true + example: + rail: ACH_CREDIT + amount: '1.00' + currency: USD + timing: 1-3 business days + StablecoinOperation: type: object required: - - cardTransactionId + - id + - stablecoinId + - operationType + - status - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/return`. Drives a `RETURN` event against an existing settled `CardTransaction`, which creates a `CardRefund` and pushes the parent transaction towards `REFUNDED` (full) or keeps it `SETTLED` (partial). + - provider + - providerEnvironment + - stablecoinProviderAccountId + - createdAt + - updatedAt properties: - cardTransactionId: + id: type: string - description: The id of the `CardTransaction` to refund against. Must have at least one settled clearing. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + description: System-generated stablecoin issuer operation identifier. + example: StablecoinOperation:019542f5-b3e7-1d02-0000-000000000301 + stablecoinId: + type: string + description: Stablecoin this operation belongs to. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + operationType: + $ref: '#/components/schemas/StablecoinOperationType' + status: + $ref: '#/components/schemas/StablecoinOperationStatus' amount: + type: string + description: Operation amount in the stablecoin's smallest unit. + example: '1000000' + provider: + $ref: '#/components/schemas/StablecoinProvider' + providerEnvironment: + $ref: '#/components/schemas/StablecoinProviderEnvironment' + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link used for the operation. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + fundingSource: + $ref: '#/components/schemas/StablecoinMintFundingSource' + source: + $ref: '#/components/schemas/StablecoinBurnSource' + fundingInstructions: + $ref: '#/components/schemas/StablecoinFundingInstructions' + destination: + $ref: '#/components/schemas/StablecoinOperationDestination' + estimatedDelivery: + $ref: '#/components/schemas/StablecoinEstimatedDelivery' + expiresAt: + type: string + format: date-time + description: Expiry for operations awaiting external funding. + example: '2026-05-27T16:40:00Z' + providerStatus: + type: string + description: Sanitized provider status when available. + example: pending + failureReason: + type: string + description: Stable internal failure code for terminal failed operations. + example: PROVIDER_TRANSFER_FAILED + description: + type: string + description: Platform-provided operation description. + example: Initial program funding mint + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' + Error503: + type: object + required: + - message + - status + - code + properties: + status: type: integer - format: int64 - description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). - exclusiveMinimum: 0 - example: 1500 - SandboxCardBalanceInquiryRequest: + enum: + - 503 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | SERVICE_UNAVAILABLE | Downstream service is temporarily unavailable | + enum: + - SERVICE_UNAVAILABLE + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + StablecoinBurnDestination: type: object required: - - merchant - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/balance_inquiry`. Drives a balance-inquiry authorization against the card. A balance inquiry is always a `0`-amount authorization, so it carries no `amount`. + - type + - externalAccountId + - rail properties: - merchant: - $ref: '#/components/schemas/CardMerchant' - SandboxCardAuthorizationAdviceRequest: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: Burn destination variant. V1 supports fiat Grid `ExternalAccount` destinations. + externalAccountId: + type: string + description: Grid bank `ExternalAccount` receiving fiat redemption proceeds. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail for the burn destination. + StablecoinBurnRequest: type: object required: - - cardTransactionId - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization_advice`. Drives an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization. + - source + - destination properties: - cardTransactionId: - type: string - description: The id of the `CardTransaction` the advice re-sizes. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 amount: - type: integer - format: int64 - description: The new *total* authorized amount, in the smallest unit of the transaction's currency, that the advice re-sizes the authorization to. - exclusiveMinimum: 0 - example: 2000 - SandboxCardTransactionRefRequest: + type: string + pattern: ^[0-9]+$ + description: Amount to burn in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + source: + $ref: '#/components/schemas/StablecoinBurnSource' + destination: + $ref: '#/components/schemas/StablecoinBurnDestination' + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Redeem ACME + StablecoinOperationListResponse: type: object required: - - cardTransactionId - description: Sandbox-only request body for simulate endpoints keyed only by an existing card transaction — currently `POST /sandbox/cards/{id}/simulate/return_reversal`. + - data + - hasMore properties: - cardTransactionId: + data: + type: array + items: + $ref: '#/components/schemas/StablecoinOperation' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: type: string - description: The id of the `CardTransaction` to act against. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - StablecoinProvider: - type: string - enum: - - BRALE - description: Stablecoin provider backing the linked account, stablecoin, or operation. + description: Cursor to retrieve the next page of results. + totalCount: + type: integer + description: Total number of stablecoin operations matching the query. StablecoinProviderAccountStatus: type: string enum: @@ -23323,12 +21094,6 @@ components: - INVALID - REVOKED description: Status of the linked stablecoin provider account credentials. - StablecoinProviderEnvironment: - type: string - enum: - - SANDBOX - - PRODUCTION - description: Provider environment derived from the authenticated Grid platform mode. StablecoinProviderAccount: type: object required: @@ -23424,9 +21189,6 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED - - INCOMING_PAYMENT.REFUND_PENDING - - INCOMING_PAYMENT.REFUND_COMPLETED - - INCOMING_PAYMENT.REFUND_FAILED - CUSTOMER.KYC_APPROVED - CUSTOMER.KYC_REJECTED - CUSTOMER.KYC_PENDING @@ -23447,11 +21209,6 @@ components: - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - CARD.FUNDING_SOURCE_CHANGE - - CARD_TRANSACTION.AUTHORIZED - - CARD_TRANSACTION.PARTIALLY_SETTLED - - CARD_TRANSACTION.SETTLED - - CARD_TRANSACTION.REFUNDED - - CARD_TRANSACTION.EXCEPTION - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -23512,9 +21269,6 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED - - INCOMING_PAYMENT.REFUND_PENDING - - INCOMING_PAYMENT.REFUND_COMPLETED - - INCOMING_PAYMENT.REFUND_FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -23557,7 +21311,6 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING - - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED @@ -23676,23 +21429,6 @@ components: type: string enum: - CARD.FUNDING_SOURCE_CHANGE - CardTransactionWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/CardTransaction' - type: - type: string - enum: - - CARD_TRANSACTION.AUTHORIZED - - CARD_TRANSACTION.PARTIALLY_SETTLED - - CARD_TRANSACTION.SETTLED - - CARD_TRANSACTION.REFUNDED - - CARD_TRANSACTION.EXCEPTION requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi.yaml b/openapi.yaml index 00db2cd55..9671b1c24 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -23,8 +23,6 @@ tags: description: Customer management endpoints for creating and updating customer information - name: Contact Verification description: Endpoints for verifying a customer's email and phone via one-time codes. Required only for customers whose payment provider mandates contact verification (e.g. EU customers); other providers return 409. - - name: Strong Customer Authentication - description: Endpoints for authorizing money-movement operations that require Strong Customer Authentication. Relevant only for customers in a region where SCA is required (e.g. EU); customers outside SCA-regulated regions never see an SCA challenge and these endpoints return 409. - name: KYC/KYB Verifications description: Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. - name: Documents @@ -60,7 +58,7 @@ tags: - name: Cards description: Card management endpoints. Issue debit cards against an internal account, freeze / unfreeze, close, manage card funding sources, and list card transactions. - name: Stablecoins - description: Stablecoin issuance endpoints. Link provider accounts, register provider-created stablecoins, create mint/burn quotes, execute them, and track the resulting operations. + description: Stablecoin issuance endpoints. Link provider accounts, register provider-created stablecoins, create direct mint/burn issuer operations, and track operation status. paths: /config: get: @@ -127,18 +125,6 @@ paths: sendFromEmailSenderName: Acme Notifications replyToEmailAddress: support@acme.com logoUrl: https://acme.com/logo.png - cardTokenization2faConfig: - displayName: Acme - logoUrl: https://acme.com/card-email-logo.png - email: - fromAddress: cards@acme.com - fromName: Acme Cards - replyToAddress: support@acme.com - subject: Your Acme card verification code - bodyText: Use this code to finish adding your Acme card to your digital wallet. - sms: - templateSid: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - bodyText: Use this code to finish adding your Acme card to your digital wallet. responses: '200': description: Configuration updated successfully @@ -426,9 +412,9 @@ paths: address: line1: 123 Market Street line2: Suite 400 - city: Seattle - state: WA - postalCode: '98101' + city: San Francisco + state: CA + postalCode: '94105' country: US responses: '201': @@ -688,9 +674,9 @@ paths: - USDC address: line1: 456 Market St - city: Seattle - state: WA - postalCode: '98101' + city: San Francisco + state: CA + postalCode: '94103' country: US businessUpdate: summary: Update business customer example @@ -705,9 +691,9 @@ paths: taxId: EIN-123456789 address: line1: 100 Technology Parkway - city: Bellevue - state: WA - postalCode: '98004' + city: Palo Alto + state: CA + postalCode: '94304' country: US embeddedWalletEmailUpdate: summary: Embedded Wallet email update request (both steps) @@ -1139,97 +1125,113 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose factors are listed or enrolled. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 + /customers/internal-accounts: get: - summary: List enrolled SCA factors + summary: List Customer internal accounts description: | - List the Strong Customer Authentication factors the customer has enrolled. + Retrieve a list of internal accounts with optional filtering parameters. Returns all + internal accounts that match the specified filters. If no filters are provided, returns all internal accounts + (paginated). - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: listScaFactors + Internal accounts are created automatically when a customer is created based on the platform configuration. + operationId: listCustomerInternalAccounts tags: - - Strong Customer Authentication + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by internal accounts associated with a specific customer + required: false + schema: + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: The customer's enrolled SCA factors. - content: - application/json: - schema: - $ref: '#/components/schemas/ScaFactorList' - '401': - description: Unauthorized + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer not found + $ref: '#/components/schemas/InternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Start SCA factor enrollment + /platform/internal-accounts: + get: + summary: List platform internal accounts description: | - Begin enrolling an SCA factor for the customer. Enrollment covers the - explicit, opt-in factors a customer chooses to add — the request body's - `type` selects `TOTP` or `PASSKEY`. Returns the factor-specific material - needed to finish via `POST /sca/factors/confirm`. - - `SMS_OTP` is implicit and is not enrolled through this endpoint. Every - customer in an SCA-regulated region has a verified phone number from - customer creation (via the Contact Verification flows — - `POST /customers/{customerId}/verify-phone` and `.../verify-phone/confirm`), - so SMS is always available as a factor with no extra setup and appears - among the customer's enrolled factors in `GET /sca/factors`. - - A customer may have **only one passkey**. Starting a passkey enrollment when - one is already enrolled returns `409` (`PASSKEY_ALREADY_ENROLLED`) — delete it - via `DELETE /sca/factors/{credentialId}` first. + Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startScaFactorEnrollment + These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. + operationId: listPlatformInternalAccounts tags: - - Strong Customer Authentication + - Internal Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaFactorEnrollRequestOneOf' + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' responses: '200': - description: Enrollment started; the factor-specific completion material is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ScaFactorEnrollStartOneOf' + $ref: '#/components/schemas/PlatformInternalAccountListResponse' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1240,52 +1242,85 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer (`CONFLICT`), or a passkey enrollment was requested while one is already enrolled (`PASSKEY_ALREADY_ENROLLED`) — only one passkey per customer is supported. + $ref: '#/components/schemas/Error500' + /customers/external-accounts: + get: + summary: List Customer external accounts + description: | + Retrieve a list of external accounts with optional filtering parameters. Returns all + external accounts that match the specified filters. If no filters are provided, returns all external accounts + (paginated). + + External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. + operationId: listCustomerExternalAccounts + tags: + - External Accounts + security: + - BasicAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by external accounts associated with a specific customer + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/ExternalAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/confirm: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer confirming a factor enrollment. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Confirm SCA factor enrollment - description: | - Finalize the factor enrollment started by `POST /sca/factors`. The request - body is discriminated by `type`: for `TOTP`, submit the shared `secret` from - the start call plus the first `code`; for `PASSKEY`, submit the WebAuthn - `credential` the device produced plus the `origin` it was produced against. - The threaded secret/credential binds the confirmation to its enrollment, so - no separate id is needed. - - A TOTP confirmation returns one-time recovery codes (shown once); a passkey - confirmation returns the enrolled factor. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the TOTP code is always `123456`. - operationId: confirmScaFactorEnrollment + summary: Add a new external account + description: Register a new external bank account for a customer. + operationId: createCustomerExternalAccount tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] requestBody: @@ -1293,16 +1328,48 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScaFactorConfirmRequestOneOf' + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Factor enrolled; the factor-specific result is returned. + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/ScaFactorConfirmResponseOneOf' + $ref: '#/components/schemas/ExternalAccount' '400': - description: Invalid or incorrect confirmation proof + description: Bad request content: application/json: schema: @@ -1313,14 +1380,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: SCA is not required for this customer. + description: Conflict - External account already exists content: application/json: schema: @@ -1331,36 +1392,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/{credentialId}: + /customers/external-accounts/{externalAccountId}: parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose factor is being deleted. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: credentialId + - name: externalAccountId in: path - description: The credential id of the enrolled factor to delete (from the factor's `credentialId`). + description: System-generated unique external account identifier required: true schema: type: string - delete: - summary: Delete an enrolled SCA factor - description: | - Delete an enrolled SCA factor by its credential id. Today only `PASSKEY` - factors carry a `credentialId` and are deletable this way. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: deleteScaFactor + get: + summary: Get customer external account by ID + description: Retrieve a customer external account by its system-generated ID + operationId: getCustomerExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] responses: - '204': - description: Factor deleted; no content is returned. + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -1368,68 +1422,28 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or factor not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/login/start: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer starting an SCA login. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Start an SCA login - description: | - Begin an SCA login for the customer with the chosen factor, opening the - end-user SCA session (an exemption gating read / account access beyond the - per-transaction window). Returns factor-specific material: `SMS_OTP` - dispatches a code and returns a `challengeId` + `expiresAt`; `TOTP` returns - only the factor (the customer reads the code from their app); `PASSKEY` - returns WebAuthn `passkeyOptions`. Complete with - `POST /sca/login/complete`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startScaLogin + delete: + summary: Delete customer external account by ID + description: Delete a customer external account by its system-generated ID + operationId: deleteCustomerExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginStartRequest' responses: - '200': - description: SCA login started; factor-specific material is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginStart' - '400': - description: Invalid or unknown factor - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -1437,63 +1451,60 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/login/complete: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer completing an SCA login. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Complete an SCA login + /platform/external-accounts: + get: + summary: List platform external accounts description: | - Finalize an SCA login by submitting the proof for the started factor - (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for - `PASSKEY`), echoing the `challengeId` for `SMS_OTP`. Returns the - reported session status. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. - In sandbox, the SMS/TOTP code is always `123456`. - operationId: completeScaLogin + These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. + operationId: listPlatformExternalAccounts tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScaLoginCompleteRequest' + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: SCA login completed; the session status is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ScaLoginComplete' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': - description: Invalid or expired proof + description: Bad request - Invalid parameters content: application/json: schema: @@ -1504,49 +1515,18 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '423': - description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. - content: - application/json: - schema: - $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/record-event: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer the security event is recorded for. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Record a security event - description: | - Record a client-side security-relevant event for the customer with Grid's risk engine (e.g. a sign-in, a sensitive view), to - feed adaptive-authentication signals. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: recordSecurityEvent + summary: Add a new platform external account + description: Register a new external bank account for the platform. + operationId: createPlatformExternalAccount tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] requestBody: @@ -1554,16 +1534,46 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecordSecurityEventRequest' + $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Event recorded; the customer's resulting login-security state is returned (including any lockout). + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/RecordSecurityEventResponse' + $ref: '#/components/schemas/ExternalAccount' '400': - description: Invalid event type + description: Bad request content: application/json: schema: @@ -1574,74 +1584,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: SCA is not required for this customer. + description: Conflict - External account already exists content: application/json: schema: $ref: '#/components/schemas/Error409' - '423': - description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. - content: - application/json: - schema: - $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset: + /platform/external-accounts/{externalAccountId}: parameters: - - name: customerId - in: query - description: The unique identifier of the customer resetting a factor. + - name: externalAccountId + in: path + description: System-generated unique external account identifier required: true schema: type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - post: - summary: Start a 2FA reset - description: | - Begin recovering a lost enrolled factor via a liveness-gated, poll-based - flow. Opens the liveness check and returns a `resetId` plus the - opaque liveness handles (`livenessAccessToken` / `verificationLink`) the end - user completes it with. Poll - `GET /sca/factors/reset/{resetId}` until liveness - passes, then call the complete endpoint. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startTwoFactorReset + get: + summary: Get platform external account by ID + description: Retrieve a platform external account by its system-generated ID + operationId: getPlatformExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStartRequest' responses: - '201': - description: Reset initiated; the reset handle and liveness material are returned. - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStart' - '400': - description: Invalid or unknown factor + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -1649,62 +1626,28 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '429': - description: Too many reset attempts. Reset initiation is rate-limited to 5 per 24 hours per customer; retry after the window indicated by `Retry-After`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset/{resetId}: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer whose reset status is polled. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: resetId - in: path - description: The reset handle returned by the start call. - required: true - schema: - type: string - get: - summary: Get 2FA reset status - description: | - Poll the status of an in-progress 2FA reset until it reaches the liveness-passed value, after which the reset can be completed. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: getTwoFactorResetStatus + delete: + summary: Delete platform external account by ID + description: Delete a platform external account by its system-generated ID + operationId: deletePlatformExternalAccountById tags: - - Strong Customer Authentication + - External Accounts security: - BasicAuth: [] responses: - '200': - description: The current reset status. - content: - application/json: - schema: - $ref: '#/components/schemas/TwoFactorResetStatus' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -1712,64 +1655,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or reset not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sca/factors/reset/{resetId}/complete: - parameters: - - name: customerId - in: query - description: The unique identifier of the customer completing the reset. - required: true - schema: - type: string - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - - name: resetId - in: path - description: The reset handle returned by the start call. - required: true - schema: - type: string + /beneficial-owners: post: - summary: Complete a 2FA reset + summary: Create a beneficial owner description: | - Complete a 2FA reset once liveness has passed, clearing the lost factor so - the customer can re-enroll. - - For an `SMS_OTP` reset, supply the new `mobile` number in the body — completing - the reset enrolls it as the customer's number. Other factors need no body. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: completeTwoFactorReset + Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. + operationId: createBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/TwoFactorResetCompleteRequest' + $ref: '#/components/schemas/BeneficialOwnerCreateRequest' responses: - '204': - description: Reset completed; no content is returned. + '201': + description: Beneficial owner created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwner' '400': - description: Reset not ready (liveness not yet passed) + description: Bad request - Invalid parameters content: application/json: schema: @@ -1781,54 +1702,57 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or reset not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/trust: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being trusted. - required: true - schema: - type: string - post: - summary: Start trusting a beneficiary + get: + summary: List beneficial owners description: | - Begin trusting (whitelisting) an external account so future sends to it can - skip the per-transaction SCA ceremony. Returns the `scaChallenge` to satisfy - (when one is issued). Complete with - `POST /customers/external-accounts/{externalAccountId}/trust/confirm`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startBeneficiaryTrust + Retrieve a list of beneficial owners for a business customer. + operationId: listBeneficialOwners tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] + parameters: + - name: customerId + in: query + description: The business customer ID + required: true + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: Beneficiary trust started; the SCA challenge (if any) is returned. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustStart' + $ref: '#/components/schemas/BeneficialOwnerListResponse' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1839,67 +1763,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/trust/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being trusted. - required: true - schema: - type: string - post: - summary: Confirm trusting a beneficiary - description: | - Finalize trusting a beneficiary (identified by the `externalAccountId` in the - path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or - `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when - one was issued. Returns `trusted: true`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the SMS/TOTP code is always `123456`. - operationId: confirmBeneficiaryTrust + /beneficial-owners/{beneficialOwnerId}: + get: + summary: Get a beneficial owner + description: Retrieve details of a specific beneficial owner by ID. + operationId: getBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' + parameters: + - name: beneficialOwnerId + in: path + description: Beneficial owner ID + required: true + schema: + type: string responses: '200': - description: Beneficiary trusted. - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirm' - '400': - description: Invalid or expired proof + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BeneficialOwner' '401': description: Unauthorized content: @@ -1907,55 +1799,47 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/untrust: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being untrusted. - required: true - schema: - type: string - post: - summary: Start untrusting a beneficiary - description: | - Begin untrusting (removing the trusted mark from) an external account, so - future sends to it are dynamically linked and require the per-transaction SCA - ceremony again. Returns the `scaChallenge` to satisfy (when one is issued). - Complete with - `POST /customers/external-accounts/{externalAccountId}/untrust/confirm`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - operationId: startBeneficiaryUntrust + patch: + summary: Update a beneficial owner + description: Update details of a specific beneficial owner. Only provided fields are updated. + operationId: updateBeneficialOwner tags: - - Strong Customer Authentication + - KYC/KYB Verifications security: - BasicAuth: [] + parameters: + - name: beneficialOwnerId + in: path + description: Beneficial owner ID + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficiary untrust started; the SCA challenge (if any) is returned. + description: Beneficial owner updated successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustStart' + $ref: '#/components/schemas/BeneficialOwner' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -1967,62 +1851,40 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/untrust/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (beneficiary) being untrusted. - required: true - schema: - type: string + /documents: post: - summary: Confirm untrusting a beneficiary + summary: Upload a document description: | - Finalize untrusting a beneficiary (identified by the `externalAccountId` in - the path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or - `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when - one was issued. Returns `trusted: false`. - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. - In sandbox, the SMS/TOTP code is always `123456`. - operationId: confirmBeneficiaryUntrust + Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. + operationId: uploadDocument tags: - - Strong Customer Authentication + - Documents security: - BasicAuth: [] requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' + $ref: '#/components/requestBodies/DocumentUploadRequestBody' responses: - '200': - description: Beneficiary untrusted. + '201': + description: Document uploaded successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficiaryTrustConfirm' + $ref: '#/components/schemas/Document' '400': - description: Invalid or expired proof + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2034,56 +1896,33 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or external account not found + description: Document holder not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/internal-accounts: get: - summary: List Customer internal accounts + summary: List documents description: | - Retrieve a list of internal accounts with optional filtering parameters. Returns all - internal accounts that match the specified filters. If no filters are provided, returns all internal accounts - (paginated). - - Internal accounts are created automatically when a customer is created based on the platform configuration. - operationId: listCustomerInternalAccounts + Retrieve a list of documents with optional filtering by document holder. + operationId: listDocuments tags: - - Internal Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId + - name: documentHolder in: query - description: Filter by internal accounts associated with a specific customer + description: Filter by document holder ID (Customer or BeneficialOwner) required: false schema: type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2105,7 +1944,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/DocumentListResponse' '400': description: Bad request - Invalid parameters content: @@ -2124,107 +1963,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/internal-accounts: + /documents/{documentId}: get: - summary: List platform internal accounts - description: | - Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - - These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. - operationId: listPlatformInternalAccounts + summary: Get a document by ID + description: Retrieve details and metadata of a specific document by ID. + operationId: getDocument tags: - - Internal Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false + - name: documentId + in: path + description: Document ID + required: true schema: type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/PlatformInternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Document' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts: - get: - summary: List Customer external accounts + put: + summary: Replace a document description: | - Retrieve a list of external accounts with optional filtering parameters. Returns all - external accounts that match the specified filters. If no filters are provided, returns all external accounts - (paginated). - - External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. - operationId: listCustomerExternalAccounts + Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. + operationId: replaceDocument tags: - - External Accounts + - Documents security: - BasicAuth: [] parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by external accounts associated with a specific customer - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false + - name: documentId + in: path + description: Document ID + required: true schema: type: string + requestBody: + $ref: '#/components/requestBodies/DocumentReplaceRequestBody' responses: '200': - description: Successful operation + description: Document replaced successfully content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/Document' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2235,79 +2041,51 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add a new external account - description: Register a new external bank account for a customer. - operationId: createCustomerExternalAccount + delete: + summary: Delete a document + description: | + Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. + operationId: deleteDocument tags: - - External Accounts + - Documents security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + parameters: + - name: documentId + in: path + description: Document ID + required: true + schema: + type: string responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Document deleted successfully '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Document not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: Conflict - Document cannot be deleted (already submitted for verification) content: application/json: schema: @@ -2318,60 +2096,110 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get customer external account by ID - description: Retrieve a customer external account by its system-generated ID - operationId: getCustomerExternalAccountById + /verifications: + post: + summary: Submit customer for verification + description: | + Trigger KYC (individual) or KYB (business) verification for a customer. + The response indicates whether all required information has been provided. + If data is missing, the `errors` array describes exactly what needs to be + supplied before verification can proceed. + + Call this endpoint again after resolving errors to re-submit. + + ### What to collect for KYB + + Before submitting a `BUSINESS` customer, collect the following via + `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: + + **Business identifying information** + - Entity full legal name + - Doing Business As (DBA) name, if applicable + - Physical address — principal place of business + - Countries of operation + - Identification number — U.S. taxpayer identification number, or, for a + foreign business without one, alternative government-issued documentation + certifying the existence of the business + + **Ownership and control structure** — collected for **one control person** + (an individual with significant responsibility to control, manage, or + direct the legal entity) **and all beneficial owners** (every individual + who owns 25% or more, directly or indirectly). For each, provide: + - Full name + - Date of birth + - Address + - Identification number: + - U.S. persons — SSN or ITIN + - Non-U.S. persons — one or more of: ITIN, passport (with country of + issuance), alien identification card, or another government-issued + photo ID evidencing nationality or residence + + **Required documents** + - Company formation and existence documents (certificate of incorporation, + articles of association, etc.) + - Proof of ownership and control structure (organization and ownership + chart, shareholder agreements, operating agreements, register of members, + or certification of controlling person and beneficial owners) + - Proof of address dated within the last 3 months (utility bill, bank + statement, lease agreement, or official correspondence) + - Tax ID or equivalent identifying-number documents + - For non-U.S. beneficial owners — passport plus one additional + government-issued ID + operationId: createVerification tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + description: | + Verification status returned. Check `verificationStatus` and `errors` to determine next steps. content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Verification' + examples: + missingInfo: + summary: Verification blocked by missing data + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000001 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_FIELD + field: customer.address.line1 + reason: Business address line 1 is required + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 + type: MISSING_FIELD + field: personalInfo.birthDate + reason: Date of birth is required for beneficial owners + createdAt: '2025-10-03T12:00:00Z' + submitted: + summary: Verification submitted successfully + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: IN_PROGRESS + errors: [] + createdAt: '2025-10-03T12:00:00Z' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete customer external account by ID - description: |- - Delete a customer external account by its system-generated ID. - An account that is currently a trusted beneficiary for SCA cannot be deleted — untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete. - operationId: deleteCustomerExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] - responses: - '204': - description: External account deleted successfully + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2379,42 +2207,39 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Returned with `BENEFICIARY_TRUSTED` when the external account is currently a trusted beneficiary. Untrust it first, then delete. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts: get: - summary: List platform external accounts + summary: List verifications description: | - Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. - - These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. - operationId: listPlatformExternalAccounts + Retrieve a list of verifications with optional filtering by customer ID and status. + operationId: listVerifications tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: currency + - name: customerId in: query - description: Filter by currency code + description: Filter by customer ID required: false schema: type: string + - name: verificationStatus + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/VerificationStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2436,7 +2261,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/VerificationListResponse' '400': description: Bad request - Invalid parameters content: @@ -2455,133 +2280,94 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add a new platform external account - description: Register a new external bank account for the platform. - operationId: createPlatformExternalAccount + /verifications/{verificationId}: + get: + summary: Get a verification + description: Retrieve details of a specific verification by ID. + operationId: getVerification tags: - - External Accounts + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + parameters: + - name: verificationId + in: path + description: Verification ID + required: true + schema: + type: string responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Verification' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - External account already exists + '404': + description: Verification not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get platform external account by ID - description: Retrieve a platform external account by its system-generated ID - operationId: getPlatformExternalAccountById + /transfer-in: + post: + summary: Create a transfer-in request + description: | + Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + operationId: createTransferIn tags: - - External Accounts + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + '201': + description: Transfer-in request created successfully content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/TransactionOneOf' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete platform external account by ID - description: Delete a platform external account by its system-generated ID - operationId: deletePlatformExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] - responses: - '204': - description: External account deleted successfully + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2589,7 +2375,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or account not found content: application/json: schema: @@ -2600,29 +2386,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners: + /transfer-out: post: - summary: Create a beneficial owner + summary: Create a transfer-out request description: | - Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. - operationId: createBeneficialOwner + Transfer funds from an internal account to an external account for a specific customer. + operationId: createTransferOut tags: - - KYC/KYB Verifications + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerCreateRequest' + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + amount: 12550 + remittanceInformation: '12345' responses: '201': - description: Beneficial owner created successfully + description: Transfer-out request created successfully content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request - Invalid parameters content: @@ -2636,7 +2442,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Customer or account not found content: application/json: schema: @@ -2647,46 +2453,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /receiver/uma/{receiverUmaAddress}: get: - summary: List beneficial owners + summary: Look up an UMA address for payment description: | - Retrieve a list of beneficial owners for a business customer. - operationId: listBeneficialOwners + Lookup a receiving UMA address to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given UMA address. + operationId: lookupUma tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: customerId - in: query - description: The business customer ID + - name: receiverUmaAddress + in: path + description: UMA address of the intended recipient required: true schema: type: string - - name: limit + - name: senderUmaAddress in: query - description: Maximum number of results to return (default 20, max 100) + description: UMA address of the sender (optional if customerId is provided) required: false schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + type: string + - name: customerId in: query - description: Cursor for pagination (returned from previous request) + description: System ID of the sender (optional if senderUmaAddress is provided) required: false schema: type: string responses: '200': - description: Successful operation + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerListResponse' + $ref: '#/components/schemas/ReceiverUmaLookupResponse' '400': - description: Bad request - Invalid parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -2697,35 +2502,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners/{beneficialOwnerId}: + /receiver/external-account/{accountId}: get: - summary: Get a beneficial owner - description: Retrieve details of a specific beneficial owner by ID. - operationId: getBeneficialOwner + summary: Look up an external account for payment + description: | + Lookup an external account by ID to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. + operationId: lookupExternalAccount tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: beneficialOwnerId + - name: accountId in: path - description: Beneficial owner ID + description: System-generated ID of the external account required: true schema: type: string + example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: '200': - description: Successful operation + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' + '400': + description: Bad request - Missing or invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2733,51 +2577,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update a beneficial owner - description: Update details of a specific beneficial owner. Only provided fields are updated. - operationId: updateBeneficialOwner + /quotes/{quoteId}: + get: + summary: Get quote by ID + description: | + Retrieve a quote by its ID. If the quote has been settled, it will include + the transaction ID. This allows clients to track the full lifecycle of a payment + from quote creation to settlement. + operationId: getQuoteById tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: beneficialOwnerId + - name: quoteId in: path - description: Beneficial owner ID + description: ID of the quote to retrieve required: true schema: type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficial owner updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwner' - '400': - description: Bad request - Invalid parameters + description: Quote retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Quote' '401': description: Unauthorized content: @@ -2785,7 +2633,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Quote not found content: application/json: schema: @@ -2796,29 +2644,125 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents: + /quotes: post: - summary: Upload a document + summary: Create a transfer quote description: | - Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. + Generate a quote for a cross-currency transfer between any combination of accounts + and UMA addresses. This endpoint handles currency exchange and provides the necessary + instructions to execute the transfer. - Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. - operationId: uploadDocument + **Transfer Types Supported:** + - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to UMA**: Transfer from an internal account to an UMA address. + - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. + + **Key Features:** + - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount + - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates + - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + + **Important:** If you are transferring funds in the same currency (no exchange required), + use the `/transfer-in` or `/transfer-out` endpoints instead. + operationId: createQuote tags: - - Documents + - Cross-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: requestBody: - $ref: '#/components/requestBodies/DocumentUploadRequestBody' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuoteRequest' + examples: + accountToAccount: + summary: Account to Account Transfer + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + lockedCurrencySide: SENDING + lockedCurrencyAmount: 10000 + description: Transfer between accounts, either internal or external. + accountToUma: + summary: Account to UMA Address Transfer + value: + lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: UMA_ADDRESS + umaAddress: $receiver@uma.domain.com + currency: EUR + lockedCurrencySide: SENDING + lockedCurrencyAmount: 1000 + description: 'Payment for invoice #1234' + realTimeFundingToSparkWallet: + summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. + value: + source: + sourceType: REALTIME_FUNDING + customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 + currency: USD + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 + lockedCurrencySide: RECEIVING + lockedCurrencyAmount: 10000 + immediatelyExecute: true + description: Bitcoin reward payout! responses: '201': - description: Document uploaded successfully + description: | + Transfer quote created successfully. The response includes exchange rates, + fees, and transfer details. For transfers involving UMA addresses, payment + instructions are also included for execution through banking systems. content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/Quote' + example: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + createdAt: '2025-10-03T12:00:00Z' + expiresAt: '2025-10-03T12:05:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: EUR + name: Euro + symbol: € + decimals: 2 + totalSendingAmount: 10000 + totalReceivingAmount: 9200 + exchangeRate: 0.92 + feesIncluded: 10 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -2829,56 +2773,227 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Document holder not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List documents + '501': + description: Not implemented + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /quotes/{quoteId}/execute: + post: + summary: Execute a quote description: | - Retrieve a list of documents with optional filtering by document holder. - operationId: listDocuments + Execute a quote by its ID. This endpoint initiates the transfer between + the source and destination accounts. + + This endpoint can only be used for quotes with a `source` which is either an internal account, + or has direct pull functionality (e.g. ACH pull with an external account). + + When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, + the request must include a `Grid-Wallet-Signature` header. The header value + is the full Grid wallet signature built over the `payloadToSign` value from + the quote's `paymentInstructions[].accountOrWalletInfo` entry with the + session private key of a verified authentication credential on the source + Embedded Wallet. + + Once executed, the quote cannot be cancelled and the transfer will be processed. + operationId: executeQuote tags: - - Documents + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: documentHolder - in: query - description: Filter by document holder ID (Customer or BeneficialOwner) - required: false + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute schema: type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + - name: Idempotency-Key + in: header required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 + type: string + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + responses: + '200': + description: | + Quote confirmed successfully. The transfer has been initiated and + the quote status has been updated. + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '400': + description: Bad request - Invalid quote ID or quote cannot be confirmed + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Quote already confirmed, expired, or in invalid state + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /transactions: + get: + summary: List transactions + description: | + Retrieve a paginated list of transactions with optional filtering. + The transactions can be filtered by customer ID, platform customer ID, UMA address, + date range, status, and transaction type. + + Card transactions are included and identified by `type: CARD`. In Sandbox this is how + you discover a `CardTransaction` id after simulating an authorization — list the + transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` + to the clearing and return simulate endpoints. + operationId: listTransactions + tags: + - Transactions + security: + - BasicAuth: [] + parameters: + - name: customerId + in: query + description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. + required: false + schema: + type: string + - name: platformCustomerId + in: query + description: Filter by platform-specific customer ID + required: false + schema: + type: string + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) + required: false + schema: + type: string + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 - name: cursor in: query description: Cursor for pagination (returned from previous request) required: false schema: type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/DocumentListResponse' + $ref: '#/components/schemas/TransactionListResponse' '400': description: Bad request - Invalid parameters content: @@ -2897,29 +3012,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents/{documentId}: + /transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string get: - summary: Get a document by ID - description: Retrieve details and metadata of a specific document by ID. - operationId: getDocument + summary: Get transaction by ID + description: Retrieve detailed information about a specific transaction. + operationId: getTransactionById tags: - - Documents + - Transactions security: - BasicAuth: [] - parameters: - - name: documentId - in: path - description: Document ID - required: true - schema: - type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: @@ -2927,7 +3042,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: @@ -2938,33 +3053,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - put: - summary: Replace a document + /transactions/{transactionId}/confirm: + post: + summary: Confirm receipt delivery description: | - Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. - operationId: replaceDocument + Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. + operationId: confirmReceiptDelivery tags: - - Documents + - Transactions security: - BasicAuth: [] parameters: - - name: documentId + - name: transactionId in: path - description: Document ID + description: Unique identifier of the transaction to confirm receipt delivery for required: true schema: type: string requestBody: - $ref: '#/components/requestBodies/DocumentReplaceRequestBody' + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Document replaced successfully + description: Receipt delivery confirmation recorded successfully content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2976,7 +3096,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: @@ -2987,25 +3107,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete a document + /transactions/{transactionId}/approve: + post: + summary: Approve a pending incoming payment description: | - Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. - operationId: deleteDocument + Approve a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously approve payments after async processing. + operationId: approvePendingPayment tags: - - Documents + - Transactions security: - BasicAuth: [] parameters: - - name: documentId + - name: transactionId in: path - description: Document ID + description: Unique identifier of the transaction to approve required: true schema: type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovePaymentRequest' responses: - '204': - description: Document deleted successfully + '200': + description: Payment approved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request - Invalid parameters or payment cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3013,13 +3151,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Document cannot be deleted (already submitted for verification) + description: Conflict - Payment is not in a pending state or has already been processed or timed out. content: application/json: schema: @@ -3030,106 +3168,39 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications: + /transactions/{transactionId}/reject: post: - summary: Submit customer for verification + summary: Reject a pending incoming payment description: | - Trigger KYC (individual) or KYB (business) verification for a customer. - The response indicates whether all required information has been provided. - If data is missing, the `errors` array describes exactly what needs to be - supplied before verification can proceed. - - Call this endpoint again after resolving errors to re-submit. - - ### What to collect for KYB - - Before submitting a `BUSINESS` customer, collect the following via - `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: - - **Business identifying information** - - Entity full legal name - - Doing Business As (DBA) name, if applicable - - Physical address — principal place of business - - Countries of operation - - Identification number — U.S. taxpayer identification number, or, for a - foreign business without one, alternative government-issued documentation - certifying the existence of the business - - **Ownership and control structure** — collected for **one control person** - (an individual with significant responsibility to control, manage, or - direct the legal entity) **and all beneficial owners** (every individual - who owns 25% or more, directly or indirectly). For each, provide: - - Full name - - Date of birth - - Address - - Identification number: - - U.S. persons — SSN or ITIN - - Non-U.S. persons — one or more of: ITIN, passport (with country of - issuance), alien identification card, or another government-issued - photo ID evidencing nationality or residence - - **Required documents** - - Company formation and existence documents (certificate of incorporation, - articles of association, etc.) - - Proof of ownership and control structure (organization and ownership - chart, shareholder agreements, operating agreements, register of members, - or certification of controlling person and beneficial owners) - - Proof of address dated within the last 3 months (utility bill, bank - statement, lease agreement, or official correspondence) - - Tax ID or equivalent identifying-number documents - - For non-U.S. beneficial owners — passport plus one additional - government-issued ID - operationId: createVerification + Reject a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously reject payments after additional processing. + operationId: rejectPendingPayment tags: - - KYC/KYB Verifications + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to reject + required: true + schema: + type: string requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/VerificationRequest' + $ref: '#/components/schemas/RejectPaymentRequest' responses: '200': - description: | - Verification status returned. Check `verificationStatus` and `errors` to determine next steps. + description: Payment rejected successfully content: application/json: schema: - $ref: '#/components/schemas/Verification' - examples: - missingInfo: - summary: Verification blocked by missing data - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000001 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_FIELD - field: customer.address.line1 - reason: Business address line 1 is required - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 - type: MISSING_FIELD - field: personalInfo.birthDate - reason: Date of birth is required for beneficial owners - createdAt: '2025-10-03T12:00:00Z' - submitted: - summary: Verification submitted successfully - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: IN_PROGRESS - errors: [] - createdAt: '2025-10-03T12:00:00Z' + $ref: '#/components/schemas/IncomingTransaction' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid parameters or payment cannot be rejected content: application/json: schema: @@ -3141,61 +3212,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List verifications + /crypto/estimate-withdrawal-fee: + post: + summary: Estimate crypto withdrawal fee description: | - Retrieve a list of verifications with optional filtering by customer ID and status. - operationId: listVerifications + Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. + operationId: estimateCryptoWithdrawalFee tags: - - KYC/KYB Verifications + - Cross-Currency Transfers security: - BasicAuth: [] - parameters: - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: verificationStatus - in: query - description: Filter by verification status - required: false - schema: - $ref: '#/components/schemas/VerificationStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' + examples: + estimateUSDCWithdrawal: + summary: Estimate fee for USDC withdrawal on Solana + value: + internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + currency: USDC + cryptoNetwork: SOLANA + amount: 1000000 + destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU responses: '200': - description: Successful operation + description: Fee estimation returned successfully content: application/json: schema: - $ref: '#/components/schemas/VerificationListResponse' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' '400': description: Bad request - Invalid parameters content: @@ -3208,43 +3273,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /verifications/{verificationId}: - get: - summary: Get a verification - description: Retrieve details of a specific verification by ID. - operationId: getVerification - tags: - - KYC/KYB Verifications - security: - - BasicAuth: [] - parameters: - - name: verificationId - in: path - description: Verification ID - required: true - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Verification' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' '404': - description: Verification not found + description: Internal account not found content: application/json: schema: @@ -3255,49 +3285,24 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-in: + /sandbox/webhooks/test: post: - summary: Create a transfer-in request - description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. - operationId: createTransferIn + summary: Send a test webhook + description: Send a test webhook to the configured endpoint + operationId: sendTestWebhook tags: - - Same-Currency Transfers + - Sandbox security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 responses: - '201': - description: Transfer-in request created successfully + '200': + description: Webhook delivered successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/TestWebhookResponse' '400': - description: Bad request - Invalid parameters + description: Bad request - Webhook delivery failed content: application/json: schema: @@ -3308,129 +3313,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-out: + /customers/bulk/csv: post: - summary: Create a transfer-out request + summary: Upload customers via CSV file description: | - Transfer funds from an internal account to an external account for a specific customer. - operationId: createTransferOut - tags: - - Same-Currency Transfers - security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 + Upload a CSV file containing customer information for bulk creation. The CSV file should follow + a specific format with required and optional columns based on customer type. + + ### CSV Format + The CSV file should have the following columns: + + Required columns for all customers: + - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) + - platformCustomerId: Your platform's unique identifier for the customer + - customerType: Either "INDIVIDUAL" or "BUSINESS" + + Required columns for individual customers: + - fullName: Individual's full name + - birthDate: Date of birth in YYYY-MM-DD format + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Required columns for business customers: + - businessLegalName: Legal name of the business + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Optional columns for all customers: + - addressLine2: Street address line 2 + - platformAccountId: Your platform's identifier for the bank account + - description: Optional description for the customer + + Optional columns for individual customers: + - email: Customer's email address + + Optional columns for business customers: + - businessRegistrationNumber: Business registration number + - businessTaxId: Tax identification number + + ### Example CSV + ```csv + umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName + john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US + acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US + ``` + + The upload process is asynchronous and will return a job ID that can be used to track progress. + You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. + operationId: uploadCustomersCsv + tags: + - Customers + security: + - BasicAuth: [] requestBody: required: true content: - application/json: + multipart/form-data: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - paymentRail: ACH - amount: 12550 - remittanceInformation: '12345' + type: object + required: + - file + properties: + file: + type: string + format: binary + description: CSV file containing customer information responses: - '201': - description: | - Transfer-out request created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionOneOf' - '400': - description: Bad request - Invalid parameters + '202': + description: CSV upload accepted for processing content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJobAccepted' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Customer or account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/uma/{receiverUmaAddress}: + /customers/bulk/jobs/{jobId}: get: - summary: Look up an UMA address for payment + summary: Get bulk import job status description: | - Lookup a receiving UMA address to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given UMA address. - operationId: lookupUma + Retrieve the current status and results of a bulk customer import job. This endpoint can be used + to track the progress of both CSV uploads. + + The response includes: + - Overall job status + - Progress statistics + - Detailed error information for failed entries + - Completion timestamp when finished + operationId: getBulkCustomerImportJob tags: - - Cross-Currency Transfers + - Customers security: - BasicAuth: [] parameters: - - name: receiverUmaAddress + - name: jobId in: path - description: UMA address of the intended recipient + description: ID of the bulk import job to retrieve required: true schema: type: string - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string responses: '200': - description: Successful lookup - content: - application/json: - schema: - $ref: '#/components/schemas/ReceiverUmaLookupResponse' - '400': - description: Bad request - Missing or invalid parameters + description: Job status retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJob' '401': description: Unauthorized content: @@ -3438,69 +3447,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). + description: Job not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/external-account/{accountId}: - get: - summary: Look up an external account for payment + /invitations: + post: + summary: Create an UMA invitation description: | - Lookup an external account by ID to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. - operationId: lookupExternalAccount + Create an UMA invitation from a given platform customer. + operationId: createInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] - parameters: - - name: accountId - in: path - description: System-generated ID of the external account - required: true - schema: - type: string - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UmaInvitationCreateRequest' responses: - '200': - description: Successful lookup + '201': + description: Invitation created successfully content: application/json: schema: - $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -3511,56 +3493,36 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: External account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}: + /invitations/{invitationCode}: get: - summary: Get quote by ID + summary: Get an UMA invitation by code description: | - Retrieve a quote by its ID. If the quote has been settled, it will include - the transaction ID. This allows clients to track the full lifecycle of a payment - from quote creation to settlement. - operationId: getQuoteById + Retrieve details about an UMA invitation by its invitation code. + operationId: getInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: quoteId + - name: invitationCode in: path - description: ID of the quote to retrieve + description: The code of the invitation to get required: true schema: type: string responses: '200': - description: Quote retrieved successfully + description: Invitation retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '401': description: Unauthorized content: @@ -3568,7 +3530,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Invitation not found content: application/json: schema: @@ -3579,133 +3541,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes: + /invitations/{invitationCode}/claim: post: - summary: Create a transfer quote + summary: Claim an UMA invitation description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. + Claim an UMA invitation by associating it with an invitee UMA address. - **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. - - **Account to UMA**: Transfer from an internal account to an UMA address. - - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - - **Key Features:** - - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount - - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + When an invitation is successfully claimed: + 1. The invitation status changes from PENDING to CLAIMED + 2. The invitee UMA address is associated with the invitation + 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. - operationId: createQuote + This endpoint allows customers to accept invitations sent to them by other UMA customers. + operationId: claimInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + - name: invitationCode + in: path + description: The code of the invitation to claim + required: true schema: type: string - example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/QuoteRequest' - examples: - accountToAccount: - summary: Account to Account Transfer - value: - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - lockedCurrencySide: SENDING - lockedCurrencyAmount: 10000 - description: Transfer between accounts, either internal or external. - accountToUma: - summary: Account to UMA Address Transfer - value: - lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: UMA_ADDRESS - umaAddress: $receiver@uma.domain.com - currency: EUR - lockedCurrencySide: SENDING - lockedCurrencyAmount: 1000 - description: 'Payment for invoice #1234' - realTimeFundingToSparkWallet: - summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. - value: - source: - sourceType: REALTIME_FUNDING - customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 - currency: USD - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 - lockedCurrencySide: RECEIVING - lockedCurrencyAmount: 10000 - immediatelyExecute: true - description: Bitcoin reward payout! + $ref: '#/components/schemas/UmaInvitationClaimRequest' responses: - '201': - description: | - Transfer quote created successfully. The response includes exchange rates, - fees, and transfer details. For transfers involving UMA addresses, payment - instructions are also included for execution through banking systems. - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - example: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - createdAt: '2025-10-03T12:00:00Z' - expiresAt: '2025-10-03T12:05:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: EUR - name: Euro - symbol: € - decimals: 2 - totalSendingAmount: 10000 - totalReceivingAmount: 9200 - exchangeRate: 0.92 - feesIncluded: 10 - platformFeesIncluded: 0 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 - '202': - description: | - Quote created but awaiting Strong Customer Authentication. Returned only for customers whose provider requires SCA (e.g. EU) on a realtime-funding source: the quote has status `PENDING_AUTHORIZATION` and carries an `scaChallenge`, and `paymentInstructions` are **withheld** until the challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once authorized, the quote's `paymentInstructions` are populated. + '200': + description: Invitation claimed successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -3716,155 +3590,89 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Invitation not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /quotes/{quoteId}/execute: + /invitations/{invitationCode}/cancel: post: - summary: Execute a quote + summary: Cancel an UMA invitation description: | - Execute a quote by its ID. This endpoint initiates the transfer between - the source and destination accounts. - - This endpoint can only be used for quotes with a `source` which is either an internal account, - or has direct pull functionality (e.g. ACH pull with an external account). + Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, - the request must include a `Grid-Wallet-Signature` header. The header value - is the full Grid wallet signature built over the `payloadToSign` value from - the quote's `paymentInstructions[].accountOrWalletInfo` entry with the - session private key of a verified authentication credential on the source - Embedded Wallet. + When an invitation is cancelled: + 1. The invitation status changes from PENDING to CANCELLED + 2. The invitation can no longer be claimed + 3. The invitation URL will show as cancelled when accessed - Once executed, the quote cannot be cancelled and the transfer will be processed. - operationId: executeQuote + Only pending invitations can be cancelled. Attempting to cancel an invitation + that is already claimed, expired, or cancelled will result in an error. + operationId: cancelInvitation tags: - - Cross-Currency Transfers + - Invitations security: - BasicAuth: [] parameters: - - name: quoteId + - name: invitationCode in: path + description: The code of the invitation to cancel required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ExecuteQuoteRequest' responses: '200': - description: | - Quote processed. The outcome depends on whether SCA applies to the customer (currently EU customers): - - - **No SCA required:** the transfer is initiated and the quote status advances (`PROCESSING` / `COMPLETED`). - - - **SCA required:** the transfer is **not** initiated yet. The quote is returned with status `PENDING_AUTHORIZATION` and an `scaChallenge`; release the transfer by authorizing the quote you already hold — `POST /quotes/{quoteId}/authorize` (re-calling `execute` returns 409). A pre-funded send is a single challenge, so one authorization releases it. The challenge (and its SMS code / passkey assertion) only comes into existence once this call initiates the transfer, so the proof is always supplied on the follow-up authorize, never inline on this call. If an SMS code lapses, re-send it via `POST /quotes/{quoteId}/authorize/resend`. + description: Invitation cancelled successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request - Invalid quote ID or quote cannot be confirmed + description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Quote not found + '403': + description: Forbidden - Only the platform which created the invitation can cancel it content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Quote already confirmed, expired, or in invalid state + $ref: '#/components/schemas/Error403' + '404': + description: Invitation not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}/authorize: - parameters: - - name: quoteId - in: path - description: The unique identifier of the quote whose SCA challenge is being authorized. - required: true - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000006 + /sandbox/send: post: - summary: Authorize a quote's SCA challenge + summary: Simulate sending funds description: | - Satisfy the Strong Customer Authentication challenge carried by a quote in - `PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof. - - This is used for realtime-funding quotes: the quote is returned with an - `scaChallenge` and **without** `paymentInstructions`; once authorized, the - quote advances and its `paymentInstructions` are populated so the customer - can fund the transfer. - - As with all SCA, a quote may require more than one authorization: after - authorizing, if the quote is still `PENDING_AUTHORIZATION` it carries the - next `scaChallenge` — authorize that too, repeating until it advances (see - `ScaChallenge`). - - This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. - - In sandbox, the SMS code is always `123456`. - operationId: authorizeQuote + Simulate sending funds to the bank account as instructed in the quote. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxSend tags: - - Strong Customer Authentication + - Sandbox security: - BasicAuth: [] requestBody: @@ -3872,16 +3680,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScaAuthorization' + $ref: '#/components/schemas/SandboxSendRequest' responses: '200': - description: Challenge authorized; the updated quote is returned. + description: Funds received successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/OutgoingTransaction' '400': - description: Invalid or expired authorization proof + description: Bad request content: application/json: schema: @@ -3892,176 +3700,178 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer, or the quote has no pending challenge to authorize. Also returned with `SCA_SESSION_REQUIRED` when the customer's SCA login session is missing or expired — re-authenticate the customer, then authorize again. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '423': - description: Returned with `ACCOUNT_LOCKED` when the customer's login is temporarily locked after too many failed authorization attempts. The customer must wait for the lock to expire before authorizing again. + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error423' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when authorization attempts for this challenge happen too frequently (for example, repeated bad codes brute-forcing the OTP). The challenge may be invalidated after too many failed attempts. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + $ref: '#/components/schemas/Error403' + '404': + description: Quote not found content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}/authorize/resend: - parameters: - - name: quoteId - in: path - description: The unique identifier of the quote whose SCA challenge code should be re-sent. - required: true - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000006 + /sandbox/uma/receive: post: - summary: Resend a quote's SCA challenge code + summary: Simulate payment send to test receiving an UMA payment description: | - Re-send the one-time code for a realtime-funding quote in - `PENDING_AUTHORIZATION` status whose `scaChallenge.factor` is `SMS_OTP`. The - existing challenge is reused — no new challenge is issued, and its - `scaChallenge.expiresAt` is **not** extended; once the challenge is past - `expiresAt` it can no longer be authorized. - - Only meaningful for customers in a region where SCA is required (e.g. EU); - a 409 is returned otherwise. `PASSKEY` challenges cannot be re-sent and return 409. - - In sandbox, the code is always `123456`. - operationId: resendQuoteScaCode - tags: - - Strong Customer Authentication + Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxReceive + tags: + - Sandbox security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '204': - description: Code re-sent. + '200': + description: Payment triggered successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Sender or receiver not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: SCA is not required for this customer, the quote has no pending SMS challenge, or the challenge uses a factor whose code cannot be re-sent (e.g. passkey). + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when codes are re-sent too frequently, to avoid spamming the customer's phone. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + $ref: '#/components/schemas/Error500' + /sandbox/internal-accounts/{accountId}/fund: + post: + summary: Simulate funding an internal account + description: | + Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxFundInternalAccount + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: accountId + in: path + required: true + description: The ID of the internal account to fund + schema: + type: string + example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxFundRequest' + examples: + fundUSDAccount: + summary: Fund USD internal account with $1,000 + value: + amount: 100000 + fundBTCAccount: + summary: Fund BTC internal account with 0.01 BTC + value: + amount: 1000000 + responses: + '200': + description: Internal account funded successfully content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/InternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions: + /uma-providers: get: - summary: List transactions + summary: List available Counterparty Providers description: | - Retrieve a paginated list of transactions with optional filtering. - The transactions can be filtered by customer ID, platform customer ID, UMA address, - date range, status, and transaction type. - - Card transactions are included and identified by `type: CARD`. In Sandbox this is how - you discover a `CardTransaction` id after simulating an authorization — list the - transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` - to the clearing and return simulate endpoints. - operationId: listTransactions + Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. + operationId: getAvailableUmaProviders tags: - - Transactions - security: - - BasicAuth: [] + - Available UMA Providers parameters: - - name: customerId - in: query - description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. - required: false - schema: - type: string - - name: platformCustomerId - in: query - description: Filter by platform-specific customer ID - required: false - schema: - type: string - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference + - in: query + name: countryCode + description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. required: false schema: type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format + example: US + - in: query + name: currencyCode + description: The ISO 4217 currency code to filter providers by supported currency. required: false schema: type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format + example: USD + - in: query + name: hasBlockedProviders + description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. required: false schema: - type: string - format: date-time + type: boolean - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -4087,19 +3897,15 @@ paths: - asc - desc default: desc + security: + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/UmaProviderListResponse' '401': description: Unauthorized content: @@ -4112,77 +3918,113 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string - get: - summary: Get transaction by ID - description: Retrieve detailed information about a specific transaction. - operationId: getTransactionById + /tokens: + post: + summary: Create a new API token + description: Create a new API token to access the Grid APIs. + operationId: createToken tags: - - Transactions + - API Tokens security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '200': - description: Successful operation + '201': + description: API token created successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '401': - description: Unauthorized + $ref: '#/components/schemas/ApiToken' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/confirm: - post: - summary: Confirm receipt delivery + get: + summary: List tokens description: | - Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. - operationId: confirmReceiptDelivery + Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match + the specified filters. If no filters are provided, returns all tokens (paginated). + operationId: listTokens tags: - - Transactions + - API Tokens security: - BasicAuth: [] parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to confirm receipt delivery for - required: true - schema: + - name: name + in: query + description: Filter by name of the token + required: false + schema: + type: string + - name: createdAfter + in: query + description: Filter customers created after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: createdBefore + in: query + description: Filter customers created before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedAfter + in: query + description: Filter customers updated after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedBefore + in: query + description: Filter customers updated before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Receipt delivery confirmation recorded successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/TokenListResponse' '400': description: Bad request - Invalid parameters content: @@ -4195,55 +4037,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/approve: - post: - summary: Approve a pending incoming payment - description: | - Approve a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously approve payments after async processing. - operationId: approvePendingPayment + /tokens/{tokenId}: + parameters: + - name: tokenId + in: path + description: System-generated unique token identifier + required: true + schema: + type: string + get: + summary: Get API token by ID + description: Retrieve an API token by their system-generated ID + operationId: getTokenById tags: - - Transactions + - API Tokens security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to approve - required: true - schema: - type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ApprovePaymentRequest' responses: '200': - description: Payment approved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - Invalid parameters or payment cannot be approved + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ApiToken' '401': description: Unauthorized content: @@ -4251,56 +4073,30 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Token not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/reject: - post: - summary: Reject a pending incoming payment - description: | - Reject a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously reject payments after additional processing. - operationId: rejectPendingPayment + delete: + summary: Delete API token by ID + description: Delete an API token by their system-generated ID + operationId: deleteTokenById tags: - - Transactions + - API Tokens security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to reject - required: true - schema: - type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/RejectPaymentRequest' responses: - '200': - description: Payment rejected successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' + '204': + description: API token deleted successfully '400': - description: Bad request - Invalid parameters or payment cannot be rejected + description: Bad request content: application/json: schema: @@ -4312,124 +4108,208 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Token not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/cancel: - post: - summary: Cancel a bank transfer + /internal-accounts/{id}: + parameters: + - name: id + in: path + description: The id of the internal account to update. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account description: | - Request cancellation of a pending bank transfer — an ACH transfer (push or pull) or a wire — before it has settled, for example a payment or collection initiated outside of the receiving bank's processing window. - Whether a transfer can still be cancelled is determined by the banking partner that is settling it: the request is forwarded to the partner's own cancellation facility, and a transfer that the partner has already processed (or that is otherwise past its cancellation window) cannot be cancelled. Cancellation applies to bank-rail transfers; requests for transaction types that cannot be cancelled are rejected. - operationId: cancelTransaction + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating wallet privacy is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount tags: - - Transactions + - Internal Accounts security: - BasicAuth: [] parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to cancel - required: true + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. schema: type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/CancelTransactionRequest' + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true responses: '200': - description: Cancellation requested successfully + description: Signed retry accepted. Returns the updated internal account. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + status: ACTIVE + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Invalid parameters or the transaction cannot be cancelled + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Internal account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - The transaction has already settled or is otherwise past the point where it can be cancelled. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /crypto/estimate-withdrawal-fee: + /internal-accounts/{id}/export: post: - summary: Estimate crypto withdrawal fee + summary: Export internal account wallet credentials description: | - Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. - operationId: estimateCryptoWithdrawalFee + Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. + + Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): + + 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. + + The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. + operationId: exportInternalAccount tags: - - Cross-Currency Transfers + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the internal account to export. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' + $ref: '#/components/schemas/InternalAccountExportRequest' examples: - estimateUSDCWithdrawal: - summary: Estimate fee for USDC withdrawal on Solana + export: + summary: Export request (both steps) value: - internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - currency: USDC - cryptoNetwork: SOLANA - amount: 1000000 - destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 responses: '200': - description: Fee estimation returned successfully + description: Signed retry accepted. Returns the encrypted wallet credentials. content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' + $ref: '#/components/schemas/InternalAccountExportResponse' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: @@ -4446,118 +4326,237 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/webhooks/test: + /auth/credentials: post: - summary: Send a test webhook - description: Send a test webhook to the configured endpoint - operationId: sendTestWebhook + summary: Create an authentication credential + description: | + Register an authentication credential for an Embedded Wallet customer. + + Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. + + Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. + operationId: createAuthCredential tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' + examples: + emailOtp: + summary: Add an email OTP credential + value: + type: EMAIL_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + smsOtp: + summary: Add an SMS OTP credential + value: + type: SMS_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oauth: + summary: Add an OAuth credential + value: + type: OAUTH + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + passkey: + summary: Add a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: iPhone Face-ID + challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx + attestation: + credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY + clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow + transports: + - internal + - hybrid responses: - '200': - description: Webhook delivered successfully + '201': + description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: - $ref: '#/components/schemas/TestWebhookResponse' + $ref: '#/components/schemas/AuthMethodResponse' + examples: + emailOtp: + summary: Email OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + smsOtp: + summary: SMS OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: SMS_OTP + nickname: '+14155550123' + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + oauth: + summary: OAuth credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + passkey: + summary: Passkey credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + '202': + description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Additional email OTP credential challenge + value: + type: EMAIL_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + smsOtp: + summary: Additional SMS OTP credential challenge + value: + type: SMS_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + passkey: + summary: Additional passkey credential challenge + value: + type: PASSKEY + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Webhook delivery failed + description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/csv: - post: - summary: Upload customers via CSV file - description: | - Upload a CSV file containing customer information for bulk creation. The CSV file should follow - a specific format with required and optional columns based on customer type. - - ### CSV Format - The CSV file should have the following columns: - - Required columns for all customers: - - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) - - platformCustomerId: Your platform's unique identifier for the customer - - customerType: Either "INDIVIDUAL" or "BUSINESS" - - Required columns for individual customers: - - fullName: Individual's full name - - birthDate: Date of birth in YYYY-MM-DD format - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) + get: + summary: List authentication credentials + description: |- + Retrieve all authentication credentials registered on an Embedded Wallet internal account. - Required columns for business customers: - - businessLegalName: Legal name of the business - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Optional columns for all customers: - - addressLine2: Street address line 2 - - platformAccountId: Your platform's identifier for the bank account - - description: Optional description for the customer - - Optional columns for individual customers: - - email: Customer's email address - - Optional columns for business customers: - - businessRegistrationNumber: Business registration number - - businessTaxId: Tax identification number - - ### Example CSV - ```csv - umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName - john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US - acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US - ``` - - The upload process is asynchronous and will return a job ID that can be used to track progress. - You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. - operationId: uploadCustomersCsv + The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. + operationId: listAuthCredentials tags: - - Customers + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: - - file - properties: - file: - type: string - format: binary - description: CSV file containing customer information + parameters: + - name: accountId + in: query + description: Internal account id whose authentication credentials to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '202': - description: CSV upload accepted for processing + '200': + description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJobAccepted' + $ref: '#/components/schemas/AuthCredentialListResponse' + examples: + multipleCredentials: + summary: Internal account with multiple authentication credentials + value: + data: + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:35:00Z' + updatedAt: '2026-04-08T15:35:00Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-09T10:15:00Z' + updatedAt: '2026-04-09T10:15:00Z' + empty: + summary: No credentials registered on the account + value: + data: [] + '400': + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -4570,45 +4569,68 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/jobs/{jobId}: - get: - summary: Get bulk import job status + /auth/credentials/{id}: + delete: + summary: Revoke an authentication credential description: | - Retrieve the current status and results of a bulk customer import job. This endpoint can be used - to track the progress of both CSV uploads. + Revoke an authentication credential on an Embedded Wallet internal account. - The response includes: - - Overall job status - - Progress statistics - - Detailed error information for failed entries - - Completion timestamp when finished - operationId: getBulkCustomerImportJob + Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: + + 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. + operationId: revokeAuthCredential tags: - - Customers + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: jobId + - name: id in: path - description: ID of the bulk import job to retrieve + description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). required: true schema: type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Job status retrieved successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJob' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Authentication credential revoked successfully. + '400': + description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Job not found + description: Authentication credential not found content: application/json: schema: @@ -4619,29 +4641,103 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations: + /auth/credentials/{id}/verify: post: - summary: Create an UMA invitation + summary: Verify an authentication credential description: | - Create an UMA invitation from a given platform customer. - operationId: createInvitation + Complete the verification step for a previously created authentication credential and issue a session. + + For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. + + For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. + + On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. + operationId: verifyAuthCredential tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationCreateRequest' + $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' + examples: + emailOtp: + summary: Verify an email OTP credential (first leg) + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + smsOtp: + summary: Verify an SMS OTP credential (first leg) + value: + type: SMS_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + emailOtpSignedRetry: + summary: Signed retry completing an email OTP verification + description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + oauth: + summary: Verify an OAuth credential + value: + type: OAUTH + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + passkey: + summary: Verify a passkey credential + value: + type: PASSKEY + assertion: + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 + authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA + signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: - '201': - description: Invitation created successfully + '200': + description: Authentication credential verified and session issued content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/AuthSession' + '202': + description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Email OTP verification challenge (sign and retry) + value: + type: EMAIL_OTP + payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4649,96 +4745,106 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Authentication credential not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}: - get: - summary: Get an UMA invitation by code - description: | - Retrieve details about an UMA invitation by its invitation code. - operationId: getInvitation - tags: - - Invitations - security: - - BasicAuth: [] - parameters: - - name: invitationCode - in: path - description: The code of the invitation to get - required: true - schema: - type: string - responses: - '200': - description: Invitation retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/UmaInvitation' - '401': - description: Unauthorized - content: - application/json: + $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. schema: - $ref: '#/components/schemas/Error401' - '404': - description: Invitation not found + type: integer content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/claim: + /auth/credentials/{id}/challenge: post: - summary: Claim an UMA invitation + summary: Re-issue an authentication credential challenge description: | - Claim an UMA invitation by associating it with an invitee UMA address. + Re-issue the challenge for an existing authentication credential. - When an invitation is successfully claimed: - 1. The invitation status changes from PENDING to CLAIMED - 2. The invitee UMA address is associated with the invitation - 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation + For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. - This endpoint allows customers to accept invitations sent to them by other UMA customers. - operationId: claimInvitation + `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + + For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. + operationId: challengeAuthCredential tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: invitationCode + - name: id in: path - description: The code of the invitation to claim + description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). required: true schema: type: string requestBody: - required: true + description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. + required: false content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationClaimRequest' + $ref: '#/components/schemas/AuthCredentialChallengeRequest' + examples: + passkey: + summary: Re-challenge a passkey credential + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + emailOtp: + summary: Re-challenge an email-OTP credential (empty body) + value: {} + smsOtp: + summary: Re-challenge an SMS-OTP credential (empty body) + value: {} responses: '200': - description: Invitation claimed successfully + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/AuthCredentialResponseOneOf' + examples: + emailOtp: + summary: Email OTP challenge re-issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + passkey: + summary: Passkey reauthentication challenge issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4752,51 +4858,57 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Invitation not found + description: Authentication credential not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/cancel: - post: - summary: Cancel an UMA invitation - description: | - Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - - When an invitation is cancelled: - 1. The invitation status changes from PENDING to CANCELLED - 2. The invitation can no longer be claimed - 3. The invitation URL will show as cancelled when accessed + /auth/sessions: + get: + summary: List active sessions + description: |- + Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. - Only pending invitations can be cancelled. Attempting to cancel an invitation - that is already claimed, expired, or cancelled will result in an error. - operationId: cancelInvitation + The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. + operationId: listAuthSessions tags: - - Invitations + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: invitationCode - in: path - description: The code of the invitation to cancel + - name: accountId + in: query + description: Internal account id whose sessions to list. required: true schema: type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Invitation cancelled successfully + description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/SessionListResponse' '400': - description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: @@ -4807,48 +4919,58 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Only the platform which created the invitation can cancel it - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Invitation not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/send: - post: - summary: Simulate sending funds + /auth/sessions/{id}: + delete: + summary: Revoke an authentication session description: | - Simulate sending funds to the bank account as instructed in the quote. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxSend + Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: + + 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. + operationId: revokeAuthSession tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxSendRequest' + parameters: + - name: id + in: path + description: The id of the session to revoke. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Funds received successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Session revoked successfully. '400': description: Bad request content: @@ -4856,19 +4978,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Session not found content: application/json: schema: @@ -4879,30 +4995,87 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/uma/receive: + /auth/sessions/{id}/refresh: post: - summary: Simulate payment send to test receiving an UMA payment + summary: Refresh an authentication session description: | - Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxReceive + Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: + + 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. + + The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. + operationId: refreshAuthSession tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxUmaReceiveRequest' - responses: - '200': - description: Payment triggered successfully - content: - application/json: + parameters: + - name: id + in: path + description: The id of the active session to refresh. + required: true + schema: + type: string + example: Session:019542f5-b3e7-1d02-0000-000000000003 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSessionRefreshRequest' + examples: + refresh: + summary: Refresh an active session + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + responses: + '201': + description: New authentication session created successfully. + content: + application/json: schema: - $ref: '#/components/schemas/IncomingTransaction' + $ref: '#/components/schemas/AuthSession' + examples: + session: + summary: Refreshed authentication session + value: + id: Session:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + expiresAt: '2026-04-08T15:50:00Z' + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Session refresh challenge + value: + payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' '400': description: Bad request content: @@ -4910,19 +5083,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Sender or receiver not found + description: Session not found content: application/json: schema: @@ -4933,47 +5100,80 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/internal-accounts/{accountId}/fund: + /auth/delegated-keys: post: - summary: Simulate funding an internal account + summary: Create a delegated signing key description: | - Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxFundInternalAccount + Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. + + Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey tags: - - Sandbox + - Embedded Wallet Auth security: - BasicAuth: [] parameters: - - name: accountId - in: path - required: true - description: The ID of the internal account to fund + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. schema: type: string - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxFundRequest' + $ref: '#/components/schemas/DelegatedKeyCreateRequest' examples: - fundUSDAccount: - summary: Fund USD internal account with $1,000 - value: - amount: 100000 - fundBTCAccount: - summary: Fund BTC internal account with 0.01 BTC + create: + summary: Delegate signing to a Grid-custodied key value: - amount: 1000000 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Card payments key responses: - '200': - description: Internal account funded successfully + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. content: application/json: schema: - $ref: '#/components/schemas/InternalAccount' + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Card payments key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' '400': description: Bad request content: @@ -4981,92 +5181,65 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token + '404': + description: Card, card funding source, or Embedded Wallet funding account not found content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Internal account not found + $ref: '#/components/schemas/Error404' + '409': + description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /uma-providers: get: - summary: List available Counterparty Providers - description: | - Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. - operationId: getAvailableUmaProviders + summary: List delegated signing keys + description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. + operationId: listDelegatedKeys tags: - - Available UMA Providers + - Embedded Wallet Auth + security: + - BasicAuth: [] parameters: - - in: query - name: countryCode - description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. - required: false - schema: - type: string - example: US - - in: query - name: currencyCode - description: The ISO 4217 currency code to filter providers by supported currency. - required: false - schema: - type: string - example: USD - - in: query - name: hasBlockedProviders - description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. - required: false - schema: - type: boolean - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + - name: accountId in: query - description: Cursor for pagination (returned from previous request) required: false + description: The id of the internal account whose delegated keys to list. schema: type: string - - name: sortOrder + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: fundingSourceId in: query - description: Order to sort results in required: false + description: The id of the card funding source whose delegated keys to list. schema: type: string - enum: - - asc - - desc - default: desc - security: - - BasicAuth: [] + example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Successful operation + description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/UmaProviderListResponse' + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -5079,28 +5252,117 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens: - post: - summary: Create a new API token - description: Create a new API token to access the Grid APIs. - operationId: createToken + /auth/delegated-keys/{id}: + get: + summary: Get a delegated signing key + description: Retrieve a delegated signing key by its system-generated id. + operationId: getDelegatedKeyById tags: - - API Tokens + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ApiTokenCreateRequest' - responses: - '201': - description: API token created successfully - content: - application/json: + parameters: + - name: id + in: path + description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + responses: + '200': + description: Successful operation + content: + application/json: schema: - $ref: '#/components/schemas/ApiToken' + $ref: '#/components/schemas/DelegatedKey' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Revoke a delegated signing key + description: | + Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. + + The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. + operationId: revokeDelegatedKey + tags: + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + responses: + '204': + description: Delegated key revoked. The key can no longer authorize signing. + '400': + description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Delegated key not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents: + post: + summary: Create an agent + description: | + Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. + operationId: createAgent + tags: + - Agent Management + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentCreateRequest' + responses: + '201': + description: Agent created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/AgentCreateResponse' '400': description: Bad request content: @@ -5120,46 +5382,56 @@ paths: schema: $ref: '#/components/schemas/Error500' get: - summary: List tokens - description: | - Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match - the specified filters. If no filters are provided, returns all tokens (paginated). - operationId: listTokens + summary: List agents + description: Retrieve a paginated list of agents for the authenticated platform. + operationId: listAgents tags: - - API Tokens + - Agent Management security: - BasicAuth: [] parameters: - - name: name + - name: customerId in: query - description: Filter by name of the token + description: Filter by customer ID required: false schema: type: string + - name: isPaused + in: query + description: Filter by paused status + required: false + schema: + type: boolean + - name: isConnected + in: query + description: Filter by connection status (whether the device code has been redeemed) + required: false + schema: + type: boolean - name: createdAfter in: query - description: Filter customers created after this timestamp (inclusive) + description: Filter agents created after this timestamp (inclusive) required: false schema: type: string format: date-time - name: createdBefore in: query - description: Filter customers created before this timestamp (inclusive) + description: Filter agents created before this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedAfter in: query - description: Filter customers updated after this timestamp (inclusive) + description: Filter agents updated after this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedBefore in: query - description: Filter customers updated before this timestamp (inclusive) + description: Filter agents updated before this timestamp (inclusive) required: false schema: type: string @@ -5185,7 +5457,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TokenListResponse' + $ref: '#/components/schemas/AgentListResponse' '400': description: Bad request - Invalid parameters content: @@ -5204,178 +5476,184 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens/{tokenId}: - parameters: - - name: tokenId - in: path - description: System-generated unique token identifier - required: true - schema: - type: string + /agents/approvals: get: - summary: Get API token by ID - description: Retrieve an API token by their system-generated ID - operationId: getTokenById + summary: List agent transaction approval requests + description: | + Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: listAgentApprovals tags: - - API Tokens + - Agent Management security: - BasicAuth: [] + parameters: + - name: agentId + in: query + description: Filter by agent ID + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ApiToken' - '401': - description: Unauthorized + $ref: '#/components/schemas/AgentActionListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Token not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete API token by ID - description: Delete an API token by their system-generated ID - operationId: deleteTokenById + /agents/me: + get: + summary: Get current agent + description: | + Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. + operationId: getAgentMe tags: - - API Tokens + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: - '204': - description: API token deleted successfully - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Agent' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Token not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}: + /agents/{agentId}: parameters: - - name: id + - name: agentId in: path - description: The id of the internal account to update. + description: System-generated unique agent identifier required: true schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + get: + summary: Get agent by ID + description: Retrieve an agent by its system-generated ID. + operationId: getAgentById + tags: + - Agent Management + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Agent' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Agent not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' patch: - summary: Update internal account - description: | - Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - - Updating wallet privacy is a two-step signed-retry flow: - - 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. - operationId: updateInternalAccount + summary: Update agent + description: Update an agent's name or paused state. + operationId: updateAgent tags: - - Internal Accounts + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountUpdateRequest' - examples: - updateWalletPrivacy: - summary: Update wallet privacy request (both steps) - value: - privateEnabled: true + $ref: '#/components/schemas/AgentUpdateRequest' responses: '200': - description: Signed retry accepted. Returns the updated internal account. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - examples: - enabled: - summary: Wallet privacy enabled - value: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: EMBEDDED_WALLET - status: ACTIVE - balance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - privateEnabled: true - createdAt: '2026-04-08T15:30:00Z' - updatedAt: '2026-04-08T15:35:02Z' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Agent updated successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Internal account update challenge - value: - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/Agent' '400': description: Bad request content: @@ -5383,13 +5661,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: @@ -5400,69 +5678,65 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}/export: - post: - summary: Export internal account wallet credentials + delete: + summary: Delete agent + description: Permanently delete an agent. Connected agent software will lose access immediately. + operationId: deleteAgent + tags: + - Agent Management + security: + - BasicAuth: [] + responses: + '204': + description: Agent deleted successfully + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Agent not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}/policy: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + patch: + summary: Update agent policy description: | - Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. - - Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): - - 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. - - The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. - operationId: exportInternalAccount + Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. + operationId: updateAgentPolicy tags: - - Internal Accounts + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the internal account to export. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountExportRequest' - examples: - export: - summary: Export request (both steps) - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/AgentPolicyUpdateRequest' responses: '200': - description: Signed retry accepted. Returns the encrypted wallet credentials. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountExportResponse' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Agent policy updated successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/Agent' '400': description: Bad request content: @@ -5470,13 +5744,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: @@ -5487,233 +5761,94 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials: + /agents/{agentId}/device-codes: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string post: - summary: Create an authentication credential + summary: Regenerate a device code description: | - Register an authentication credential for an Embedded Wallet customer. - - Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. - - Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. - operationId: createAuthCredential + Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. + operationId: regenerateAgentDeviceCode tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' - examples: - emailOtp: - summary: Add an email OTP credential - value: - type: EMAIL_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - smsOtp: - summary: Add an SMS OTP credential - value: - type: SMS_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oauth: - summary: Add an OAuth credential - value: - type: OAUTH - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - passkey: - summary: Add a passkey credential - value: - type: PASSKEY - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: iPhone Face-ID - challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx - attestation: - credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY - clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 - attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow - transports: - - internal - - hybrid responses: '201': - description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthMethodResponse' - examples: - emailOtp: - summary: Email OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - smsOtp: - summary: SMS OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: SMS_OTP - nickname: '+14155550123' - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - oauth: - summary: OAuth credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - passkey: - summary: Passkey credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - '202': - description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + description: New device code generated successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Additional email OTP credential challenge - value: - type: EMAIL_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - smsOtp: - summary: Additional SMS OTP credential challenge - value: - type: SMS_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - oauth: - summary: Additional OAuth credential challenge - value: - type: OAUTH - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - passkey: - summary: Additional passkey credential challenge - value: - type: PASSKEY - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/AgentDeviceCode' '400': - description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Agent not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Agent already has an active connection and cannot regenerate a device code + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List authentication credentials - description: |- - Retrieve all authentication credentials registered on an Embedded Wallet internal account. - - The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. - operationId: listAuthCredentials + /agents/{agentId}/actions/{actionId}/approve: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to approve + required: true + schema: + type: string + post: + summary: Approve an agent action + description: | + Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. + For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: approveAgentAction tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: accountId - in: query - description: Internal account id whose authentication credentials to list. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. + description: Action approved successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialListResponse' - examples: - multipleCredentials: - summary: Internal account with multiple authentication credentials - value: - data: - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:35:00Z' - updatedAt: '2026-04-08T15:35:00Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-09T10:15:00Z' - updatedAt: '2026-04-09T10:15:00Z' - empty: - summary: No credentials registered on the account - value: - data: [] + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Action cannot be approved content: application/json: schema: @@ -5724,352 +5859,270 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Agent or action not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Action is not pending approval or has already been processed + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}: - delete: - summary: Revoke an authentication credential + /agents/{agentId}/actions/{actionId}/reject: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to reject + required: true + schema: + type: string + post: + summary: Reject an agent action description: | - Revoke an authentication credential on an Embedded Wallet internal account. - - Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: - - 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. - operationId: revokeAuthCredential + Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: rejectAgentAction tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/AgentActionRejectRequest' responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. + '200': + description: Action rejected successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Authentication credential revoked successfully. + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + description: Bad request - Action cannot be rejected content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Action is not pending approval or has already been processed + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/verify: - post: - summary: Verify an authentication credential + /agents/device-codes/{code}/status: + parameters: + - name: code + in: path + description: The device code to check + required: true + schema: + type: string + get: + summary: Get device code status description: | - Complete the verification step for a previously created authentication credential and issue a session. - - For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. - In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. - - For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. - - On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. - operationId: verifyAuthCredential + Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. + operationId: getAgentDeviceCodeStatus tags: - - Embedded Wallet Auth + - Agent Management security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' - examples: - emailOtp: - summary: Verify an email OTP credential (first leg) - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - smsOtp: - summary: Verify an SMS OTP credential (first leg) - value: - type: SMS_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - emailOtpSignedRetry: - summary: Signed retry completing an email OTP verification - description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - oauth: - summary: Verify an OAuth credential - value: - type: OAUTH - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - passkey: - summary: Verify a passkey credential - value: - type: PASSKEY - assertion: - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 - authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA - signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Authentication credential verified and session issued - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - '202': - description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Email OTP verification challenge (sign and retry) - value: - type: EMAIL_OTP - payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' '401': - description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Device code not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/challenge: + /agents/device-codes/{code}/redeem: + parameters: + - name: code + in: path + description: The device code to redeem + required: true + schema: + type: string post: - summary: Re-issue an authentication credential challenge + summary: Redeem device code description: | - Re-issue the challenge for an existing authentication credential. - - For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. - - `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. - - For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. - operationId: challengeAuthCredential + Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. + This endpoint does not require platform authentication — the device code itself serves as proof of authorization. + operationId: redeemAgentDeviceCode tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - requestBody: - description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialChallengeRequest' - examples: - passkey: - summary: Re-challenge a passkey credential - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - emailOtp: - summary: Re-challenge an email-OTP credential (empty body) - value: {} - smsOtp: - summary: Re-challenge an SMS-OTP credential (empty body) - value: {} + - Agent Management + security: [] responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Device code redeemed successfully content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' - examples: - emailOtp: - summary: Email OTP challenge re-issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - passkey: - summary: Passkey reauthentication challenge issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' '400': - description: Bad request + description: Bad request (e.g., code already redeemed or expired) content: application/json: schema: $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Device code not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions: + /agents/me/transactions: get: - summary: List active sessions - description: |- - Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. - - The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. - operationId: listAuthSessions + summary: List agent transactions + description: | + Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. + operationId: agentListTransactions tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: accountId + - name: accountIdentifier in: query - description: Internal account id whose sessions to list. - required: true + description: Filter by account identifier (matches either sender or receiver) + required: false schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/SessionListResponse' + $ref: '#/components/schemas/TransactionListResponse' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Invalid parameters content: application/json: schema: @@ -6086,66 +6139,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}: - delete: - summary: Revoke an authentication session + /agents/me/transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get agent transaction by ID description: | - Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: - - 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. - operationId: revokeAuthSession + Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. + operationId: agentGetTransaction tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the session to revoke. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + - AgentAuth: [] responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Session revoked successfully. - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Session not found + description: Transaction not found content: application/json: schema: @@ -6156,205 +6181,186 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}/refresh: + /agents/me/quotes: post: - summary: Refresh an authentication session + summary: Create a transfer quote description: | - Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: - - 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. - - The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. - operationId: refreshAuthSession + Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. + If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. + operationId: agentCreateQuote tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - description: The id of the active session to refresh. - required: true - schema: - type: string - example: Session:019542f5-b3e7-1d02-0000-000000000003 - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id + - name: Idempotency-Key in: header required: false - description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthSessionRefreshRequest' - examples: - refresh: - summary: Refresh an active session - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/QuoteRequest' responses: '201': - description: New authentication session created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - examples: - session: - summary: Refreshed authentication session - value: - id: Session:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - expiresAt: '2026-04-08T15:50:00Z' - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + description: Transfer quote created successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Session refresh challenge - value: - payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Session not found + '403': + description: Forbidden - Agent policy does not permit this operation content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error403' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys: + /agents/me/quotes/{quoteId}: + parameters: + - name: quoteId + in: path + description: ID of the quote to retrieve + required: true + schema: + type: string + get: + summary: Get agent quote by ID + description: | + Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. + operationId: agentGetQuote + tags: + - Agent Operations + security: + - AgentAuth: [] + responses: + '200': + description: Quote retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/quotes/{quoteId}/execute: + parameters: + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Create a delegated signing key + summary: Execute a quote description: | - Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: - - 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. - - 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. - - The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. - - Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. - operationId: createDelegatedKey + Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. + If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + Once executed, the quote cannot be cancelled. + operationId: agentExecuteQuote tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: Grid-Wallet-Signature + - name: Idempotency-Key in: header required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id + example: + - name: Grid-Wallet-Signature in: header required: false - description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyCreateRequest' - examples: - create: - summary: Delegate signing to a Grid-custodied key - value: - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: Card payments key + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 responses: - '201': - description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKey' - examples: - delegatedKey: - summary: Active delegated key - value: - id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 - nickname: Card payments key - status: ACTIVE - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:42Z' - '202': - description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + '200': + description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Quote cannot be executed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Card, card funding source, or Embedded Wallet funding account not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + description: Conflict - Quote already executed, expired, or in invalid state content: application/json: schema: @@ -6365,42 +6371,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/actions: get: - summary: List delegated signing keys - description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. - operationId: listDelegatedKeys + summary: List agent's own actions + description: | + Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. + operationId: agentListActions tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: accountId + - name: status in: query + description: Filter by action status required: false - description: The id of the internal account whose delegated keys to list. schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - name: fundingSourceId + $ref: '#/components/schemas/AgentActionStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor in: query + description: Cursor for pagination (returned from previous request) required: false - description: The id of the card funding source whose delegated keys to list. schema: type: string - example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyListResponse' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentActionListResponse' '401': description: Unauthorized content: @@ -6413,30 +6422,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys/{id}: + /agents/me/actions/{actionId}: + parameters: + - name: actionId + in: path + description: Unique identifier of the agent action + required: true + schema: + type: string get: - summary: Get a delegated signing key - description: Retrieve a delegated signing key by its system-generated id. - operationId: getDelegatedKeyById + summary: Get an agent action + description: | + Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. + operationId: agentGetAction tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/DelegatedKey' + $ref: '#/components/schemas/AgentAction' '401': description: Unauthorized content: @@ -6444,7 +6453,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Delegated key not found + description: Action not found content: application/json: schema: @@ -6455,42 +6464,69 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Revoke a delegated signing key + /agents/me/transfer-in: + post: + summary: Create a transfer-in description: | - Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. - - The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. - operationId: revokeDelegatedKey + Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. + operationId: agentCreateTransferIn tags: - - Embedded Wallet Auth + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). - required: true + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '204': - description: Delegated key revoked. The key can no longer authorize signing. + '201': + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + content: + application/json: + schema: + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Delegated key not found + description: Account not found content: application/json: schema: @@ -6501,31 +6537,50 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents: + /agents/me/transfer-out: post: - summary: Create an agent + summary: Create a transfer-out description: | - Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. - operationId: createAgent + Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + operationId: agentCreateTransferOut tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentCreateRequest' + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + amount: 12550 responses: '201': - description: Agent created successfully + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. content: application/json: schema: - $ref: '#/components/schemas/AgentCreateResponse' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -6536,67 +6591,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/internal-accounts: get: - summary: List agents - description: Retrieve a paginated list of agents for the authenticated platform. - operationId: listAgents + summary: List agent's internal accounts + description: | + Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. + operationId: agentListInternalAccounts tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: isPaused - in: query - description: Filter by paused status - required: false - schema: - type: boolean - - name: isConnected - in: query - description: Filter by connection status (whether the device code has been redeemed) - required: false - schema: - type: boolean - - name: createdAfter - in: query - description: Filter agents created after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: createdBefore - in: query - description: Filter agents created before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedAfter + - name: currency in: query - description: Filter agents updated after this timestamp (inclusive) + description: Filter by currency code required: false schema: type: string - format: date-time - - name: updatedBefore + - name: type in: query - description: Filter agents updated before this timestamp (inclusive) + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. required: false schema: - type: string - format: date-time + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6618,13 +6653,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/InternalAccountListResponse' '401': description: Unauthorized content: @@ -6637,43 +6666,23 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/approvals: + /agents/me/external-accounts: get: - summary: List agent transaction approval requests + summary: List agent external accounts description: | - Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: listAgentApprovals + Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentListExternalAccounts tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: agentId - in: query - description: Filter by agent ID - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate + - name: currency in: query - description: Filter by end date (inclusive) in ISO 8601 format + description: Filter by currency code required: false schema: type: string - format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6689,23 +6698,13 @@ paths: required: false schema: type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -6724,58 +6723,105 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me: - get: - summary: Get current agent + post: + summary: Add an external account description: | - Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. - operationId: getAgentMe + Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. + operationId: agentCreateExternalAccount tags: - Agent Operations security: - AgentAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Successful operation + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': + '409': + description: Conflict - External account already exists + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}: + /agents/me/external-accounts/{externalAccountId}: parameters: - - name: agentId + - name: externalAccountId in: path - description: System-generated unique agent identifier + description: System-generated unique external account identifier required: true schema: type: string get: - summary: Get agent by ID - description: Retrieve an agent by its system-generated ID. - operationId: getAgentById + summary: Get agent external account by ID + description: | + Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentGetExternalAccount tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: @@ -6783,7 +6829,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: External account not found content: application/json: schema: @@ -6794,33 +6840,18 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update agent - description: Update an agent's name or paused state. - operationId: updateAgent + delete: + summary: Delete agent external account + description: | + Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentDeleteExternalAccount tags: - - Agent Management + - Agent Operations security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentUpdateRequest' + - AgentAuth: [] responses: - '200': - description: Agent updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -6828,7 +6859,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: External account not found content: application/json: schema: @@ -6839,67 +6870,134 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent - description: Permanently delete an agent. Connected agent software will lose access immediately. - operationId: deleteAgent + /cards: + post: + summary: Issue a card + description: | + Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + + If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. + + New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). + operationId: createCard tags: - - Agent Management + - Cards security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardCreateRequest' + examples: + virtualCard: + summary: Issue a virtual card with one funding source + value: + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '204': - description: Agent deleted successfully - '401': - description: Unauthorized + '201': + description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found + $ref: '#/components/schemas/Card' + '400': + description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/policy: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - patch: - summary: Update agent policy + '501': + description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + get: + summary: List cards description: | - Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. - operationId: updateAgentPolicy + Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. + operationId: listCards tags: - - Agent Management + - Cards security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentPolicyUpdateRequest' + parameters: + - name: cardholderId + in: query + description: Filter by cardholder (customer) id. + required: false + schema: + type: string + - name: accountId + in: query + description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. + required: false + schema: + type: string + - name: platformCardId + in: query + description: Filter by platform-specific card identifier. + required: false + schema: + type: string + - name: state + in: query + description: Filter by card state. + required: false + schema: + $ref: '#/components/schemas/CardState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Agent policy updated successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/CardListResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -6910,48 +7008,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/device-codes: + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}: parameters: - - name: agentId + - name: id in: path - description: System-generated unique agent identifier + description: System-generated unique card identifier required: true schema: type: string - post: - summary: Regenerate a device code - description: | - Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. - operationId: regenerateAgentDeviceCode + get: + summary: Get a card + description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. + operationId: getCardById tags: - - Agent Management + - Cards security: - BasicAuth: [] responses: - '201': - description: New device code generated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentDeviceCode' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Card' '401': description: Unauthorized content: @@ -6959,75 +7050,128 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Card not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Agent already has an active connection and cannot regenerate a device code - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/approve: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to approve - required: true - schema: - type: string - post: - summary: Approve an agent action + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + patch: + summary: Update a card description: | - Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. - For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: approveAgentAction + Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. + + - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. + - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. + + Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): + + 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + + Effects: + - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. + - `state: ACTIVE`: normal authorization behavior resumes. + - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. + - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + + The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + operationId: updateCardById tags: - - Agent Management + - Cards security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardUpdateRequest' + examples: + freeze: + summary: Freeze an active card + value: + state: FROZEN + unfreeze: + summary: Unfreeze a frozen card + value: + state: ACTIVE + updateFundingSources: + summary: Replace the card's bound funding sources + value: + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + freezeAndUpdateSources: + summary: Freeze the card and replace its funding sources in one call + value: + state: FROZEN + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + close: + summary: Permanently close the card + value: + state: CLOSED responses: '200': - description: Action approved successfully. Returns the updated AgentAction. + description: Signed retry accepted. Returns the updated card. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/Card' + '202': + description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request - Action cannot be approved + description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent or action not found + description: Card not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Action is not pending approval or has already been processed + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: @@ -7038,105 +7182,148 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/reject: + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}/reveal: parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId + - name: id in: path - description: Unique identifier of the agent action to reject + description: System-generated unique card identifier required: true schema: type: string post: - summary: Reject an agent action - description: | - Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: rejectAgentAction + summary: Reveal card details + description: |- + Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. + + Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. + + Every reveal is audit-logged with the requesting actor. + operationId: revealCard tags: - - Agent Management + - Cards security: - BasicAuth: [] - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionRejectRequest' responses: '200': - description: Action rejected successfully. Returns the updated AgentAction. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Action cannot be rejected + description: Reveal URL minted. content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/CardRevealResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent or action not found + '403': + description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed + $ref: '#/components/schemas/Error403' + '404': + description: Card not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/status: - parameters: - - name: code - in: path - description: The device code to check - required: true - schema: - type: string - get: - summary: Get device code status + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /sandbox/cards/{id}/simulate/authorization: + post: + summary: Simulate a card authorization description: | - Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. - operationId: getAgentDeviceCodeStatus + Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. + + The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: + + | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | + + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorization tags: - - Agent Management + - Sandbox security: - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate an authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + examples: + coffeeAuth: + summary: Approved $12.50 auth at a coffee shop + value: + amount: 1250 + currency: + code: USD + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + declinedInsufficientFunds: + summary: Declined — insufficient funds (descriptor suffix `002`) + value: + amount: 50000 + currency: + code: USD + merchant: + descriptor: AMAZON RETAIL US-002 + mcc: '5942' + country: US responses: '200': - description: Successful operation + description: Simulated authorization processed. Returns the resulting card transaction. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' + $ref: '#/components/schemas/CardTransaction' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Device code not found + description: Card not found (also returned in production for this path) content: application/json: schema: @@ -7147,38 +7334,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/redeem: - parameters: - - name: code - in: path - description: The device code to redeem - required: true - schema: - type: string + /sandbox/cards/{id}/simulate/clearing: post: - summary: Redeem device code + summary: Simulate a card clearing description: | - Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. - This endpoint does not require platform authentication — the device code itself serves as proof of authorization. - operationId: redeemAgentDeviceCode + Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. + + - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). + - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. + - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). + + Production returns `404` on this path. + operationId: sandboxSimulateCardClearing tags: - - Agent Management - security: [] + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the clearing applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardClearingRequest' + examples: + tipOnTopClearing: + summary: Clearing larger than auth — exercises post-hoc pull + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + authorizationExpiry: + summary: Clearing of 0 — exercises authorization expiry + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 0 responses: '200': - description: Device code redeemed successfully + description: Simulated clearing processed. Returns the updated card transaction. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + $ref: '#/components/schemas/CardTransaction' '400': - description: Bad request (e.g., code already redeemed or expired) + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Device code not found + description: Card or card transaction not found content: application/json: schema: @@ -7189,67 +7412,160 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions: - get: - summary: List agent transactions + /sandbox/cards/{id}/simulate/return: + post: + summary: Simulate a card return description: | - Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. - operationId: agentListTransactions + Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). + + Production returns `404` on this path. + operationId: sandboxSimulateCardReturn tags: - - Agent Operations + - Sandbox security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false + - name: id + in: path + required: true + description: The id of the card the return applies to. schema: type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardReturnRequest' + examples: + fullRefund: + summary: Full refund of a $15.00 settled transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + responses: + '200': + description: Simulated return processed. Returns the updated card transaction. + content: + application/json: + schema: + $ref: '#/components/schemas/CardTransaction' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins: + post: + summary: Register an existing provider-created stablecoin + description: | + Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. + operationId: registerStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this request safely. + required: true schema: type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinRegisterRequest' + responses: + '201': + description: Stablecoin registered + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Referenced stablecoin provider account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoins + description: Retrieve stablecoins registered to the authenticated platform. + operationId: listStablecoins + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: issuanceStatus in: query - description: Filter by reference required: false schema: - type: string - - name: startDate + $ref: '#/components/schemas/StablecoinIssuanceStatus' + - name: gridOperationsStatus in: query - description: Filter by start date (inclusive) in ISO 8601 format required: false schema: - type: string - format: date-time - - name: endDate + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + - name: provider in: query - description: Filter by end date (inclusive) in ISO 8601 format required: false schema: - type: string - format: date-time + $ref: '#/components/schemas/StablecoinProvider' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7261,27 +7577,17 @@ paths: default: 20 - name: cursor in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in + description: Opaque cursor returned as `nextCursor` from the previous response. required: false schema: type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/StablecoinListResponse' '400': description: Bad request - Invalid parameters content: @@ -7300,30 +7606,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions/{transactionId}: + /stablecoins/{stablecoinId}: parameters: - - name: transactionId + - name: stablecoinId in: path - description: Unique identifier of the transaction + description: System-generated stablecoin identifier required: true schema: type: string get: - summary: Get agent transaction by ID - description: | - Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. - operationId: agentGetTransaction + summary: Get a stablecoin + description: Retrieve a single registered stablecoin by its Grid identifier. + operationId: getStablecoin tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Stablecoin' '401': description: Unauthorized content: @@ -7331,7 +7636,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Stablecoin not found content: application/json: schema: @@ -7342,41 +7647,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes: - post: - summary: Create a transfer quote - description: | - Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. - If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. - operationId: agentCreateQuote + /stablecoins/{stablecoinId}/mints: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin mint operation + description: | + Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinMint tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Required idempotency key for retrying this mint request safely. + required: true schema: type: string - example: + maxLength: 255 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/QuoteRequest' + $ref: '#/components/schemas/StablecoinMintRequest' responses: '201': - description: Transfer quote created successfully + description: Mint operation created content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/StablecoinOperation' '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: @@ -7387,141 +7697,88 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '412': - description: Counterparty doesn't support UMA version + '404': + description: Stablecoin or referenced account not found content: application/json: schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + $ref: '#/components/schemas/Error404' + '409': + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}: - parameters: - - name: quoteId - in: path - description: ID of the quote to retrieve - required: true - schema: - type: string - get: - summary: Get agent quote by ID - description: | - Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. - operationId: agentGetQuote - tags: - - Agent Operations - security: - - AgentAuth: [] - responses: - '200': - description: Quote retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '503': + description: Provider temporarily unavailable content: application/json: schema: - $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}/execute: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/burns: parameters: - - name: quoteId + - name: stablecoinId in: path + description: System-generated stablecoin identifier required: true - description: The unique identifier of the quote to execute schema: type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Execute a quote + summary: Create a stablecoin burn operation description: | - Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. - If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - Once executed, the quote cannot be cancelled. - operationId: agentExecuteQuote + Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinBurn tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + description: Required idempotency key for retrying this burn request safely. + required: true schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinBurnRequest' responses: - '200': - description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' + '201': + description: Burn operation created content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinOperation' '400': - description: Bad request - Quote cannot be executed + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Stablecoin or referenced account not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Quote already executed, expired, or in invalid state + description: Conflict content: application/json: schema: @@ -7532,23 +7789,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions: + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/operations: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string get: - summary: List agent's own actions - description: | - Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. - operationId: agentListActions + summary: List stablecoin issuer operations + description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. + operationId: listStablecoinOperations tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: status - in: query - description: Filter by action status - required: false - schema: - $ref: '#/components/schemas/AgentActionStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7560,7 +7823,7 @@ paths: default: 20 - name: cursor in: query - description: Cursor for pagination (returned from previous request) + description: Opaque cursor returned as `nextCursor` from the previous response. required: false schema: type: string @@ -7570,43 +7833,48 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/StablecoinOperationListResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions/{actionId}: + /stablecoin-operations/{stablecoinOperationId}: parameters: - - name: actionId + - name: stablecoinOperationId in: path - description: Unique identifier of the agent action + description: System-generated stablecoin issuer operation identifier required: true schema: type: string get: - summary: Get an agent action - description: | - Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. - operationId: agentGetAction + summary: Get a stablecoin issuer operation + description: Retrieve a single stablecoin issuer operation by its Grid identifier. + operationId: getStablecoinOperation tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinOperation' '401': description: Unauthorized content: @@ -7614,7 +7882,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Action not found + description: Stablecoin operation not found content: application/json: schema: @@ -7625,51 +7893,39 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: + /stablecoin-provider-accounts: post: - summary: Create a transfer-in + summary: Link a stablecoin provider account description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn + Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. + operationId: linkStablecoinProviderAccount tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - name: Idempotency-Key in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. + required: true schema: type: string - example: 550e8400-e29b-41d4-a716-446655440000 + maxLength: 255 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 + $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' responses: '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + description: Stablecoin provider account linked content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/StablecoinProviderAccount' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -7680,119 +7936,37 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found + '409': + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: - post: - summary: Create a transfer-out - description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut + get: + summary: List stablecoin provider account links + description: Retrieve stablecoin provider account links for the authenticated platform. + operationId: listStablecoinProviderAccounts tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header + - name: provider + in: query required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/internal-accounts: - get: - summary: List agent's internal accounts - description: | - Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. - operationId: agentListInternalAccounts - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: type + $ref: '#/components/schemas/StablecoinProvider' + - name: status in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. required: false schema: - $ref: '#/components/schemas/InternalAccountType' + $ref: '#/components/schemas/StablecoinProviderAccountStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7814,7 +7988,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/StablecoinProviderAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -7827,4648 +8007,2114 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts: + /stablecoin-provider-accounts/{stablecoinProviderAccountId}: + parameters: + - name: stablecoinProviderAccountId + in: path + description: System-generated stablecoin provider account link identifier + required: true + schema: + type: string get: - summary: List agent external accounts - description: | - Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentListExternalAccounts + summary: Get a stablecoin provider account link + description: Retrieve a stablecoin provider account link by id for the authenticated platform. + operationId: getStablecoinProviderAccount tags: - - Agent Operations + - Stablecoins security: - - AgentAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/StablecoinProviderAccount' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin provider account link not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' +webhooks: + agent-action: post: - summary: Add an external account + summary: Agent action pending approval webhook description: | - Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. - operationId: agentCreateExternalAccount + Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: agentActionWebhook tags: - - Agent Operations + - Webhooks security: - - AgentAuth: [] + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' + $ref: '#/components/schemas/AgentActionWebhook' examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet + pendingApproval: + summary: Agent action pending approval value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: AGENT_ACTION.PENDING_APPROVAL + timestamp: '2025-10-03T15:00:00Z' + data: + id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 + agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 + platformCustomerId: user-a1b2c3 + status: PENDING_APPROVAL + type: EXECUTE_QUOTE + quote: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + expiresAt: '2025-10-03T15:00:30Z' + createdAt: '2025-10-03T15:00:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: INR + name: Indian Rupee + symbol: ₹ + decimals: 2 + totalSendingAmount: 50000 + totalReceivingAmount: 4625000 + exchangeRate: 92.5 + feesIncluded: 250 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 + createdAt: '2025-10-03T15:00:00Z' + updatedAt: '2025-10-03T15:00:00Z' responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '200': + description: Webhook received and acknowledged. '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - External account already exists + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get agent external account by ID - description: | - Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentGetExternalAccount + incoming-payment: + post: + summary: Incoming payment webhook and approval mechanism + description: | + Webhook that is called when an incoming payment is received by a customer's UMA address. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Payment Approval Flow + When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: + + 1. The client should check the `counterpartyInformation` against their requirements + 2. To APPROVE the payment synchronously, return a 200 OK response + 3. To REJECT the payment, return a 403 Forbidden response with an Error object + 4. To request more information, return a 422 Unprocessable Entity with specific missing fields + 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. + + The Grid system will proceed or cancel the payment based on your response. + + For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. + operationId: incomingPaymentWebhook tags: - - Agent Operations + - Webhooks security: - - AgentAuth: [] + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhook' + examples: + pendingPayment: + summary: Pending payment example requiring approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: PENDING + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + counterpartyInformation: + FULL_NAME: John Sender + BIRTH_DATE: '1985-06-15' + NATIONALITY: US + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: POSTAL_ADDRESS + mandatory: false + incomingCompletedPayment: + summary: Completed payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + incomingCompletedCryptoPayment: + summary: Completed payment funded from an external crypto wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + type: INCOMING + direction: CREDIT + source: + sourceType: REALTIME_FUNDING + currency: USDC + onChainTransaction: + transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf + network: SOLANA + destination: + destinationType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC deposit from self-custody wallet responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '401': - description: Unauthorized + description: | + Webhook received successfully. + For PENDING transactions, this indicates approval to proceed with the payment. + If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + $ref: '#/components/schemas/IncomingPaymentWebhookResponse' + '202': + description: | + Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in + cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. + The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent external account - description: | - Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentDeleteExternalAccount - tags: - - Agent Operations - security: - - AgentAuth: [] - responses: - '204': - description: External account deleted successfully - '401': - description: Unauthorized + $ref: '#/components/schemas/Error401' + '403': + description: | + Forbidden - Payment rejected by the client. + Only applicable for PENDING transactions. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: External account not found + $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/Error409' + '422': + description: | + Unprocessable Entity - Additional counterparty information required. + Only applicable for PENDING transactions. content: application/json: schema: - $ref: '#/components/schemas/Error500' - /cards: + $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' + outgoing-payment: post: - summary: Issue a card + summary: Outgoing payment status webhook description: | - Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + Webhook that is called when an outgoing payment's status changes. + This endpoint should be implemented by clients of the Grid API. - If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). - operationId: createCard + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. + operationId: outgoingPaymentWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardCreateRequest' + $ref: '#/components/schemas/OutgoingPaymentWebhook' examples: - virtualCard: - summary: Issue a virtual card with one funding source + outgoingCompletedPayment: + summary: Completed outgoing payment value: - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - responses: - '201': - description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '400': - description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivedAmount: + amount: 9706 + currency: + code: EUR + name: Euro + symbol: € + decimals: 2 + exchangeRate: 0.92 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: + counterpartyMultiplier: 1.08 + counterpartyFixedFee: 10 + gridApiMultiplier: 0.925 + gridApiFixedFee: 10 + gridApiVariableFeeRate: 0.003 + gridApiVariableFeeAmount: 30 + outgoingCompletedCryptoPayment: + summary: Completed crypto payout to an external wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000009 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 + onChainTransaction: + transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx + network: SOLANA + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + sentAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC withdrawal to self-custody wallet + paymentInstructions: [] + failedPayment: + summary: Failed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + failureReason: QUOTE_EXECUTION_FAILED + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - get: - summary: List cards + $ref: '#/components/schemas/Error409' + test-webhook: + post: + summary: Test webhook for integration verification description: | - Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. - operationId: listCards + Webhook that is sent once to verify your webhook endpoint is correctly set up. + This is sent when you configure or update your platform settings with a webhook URL. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is purely for testing your endpoint integration and signature verification. + operationId: testWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] - parameters: - - name: cardholderId - in: query - description: Filter by cardholder (customer) id. - required: false - schema: - type: string - - name: accountId - in: query - description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. - required: false - schema: - type: string - - name: platformCardId - in: query - description: Filter by platform-specific card identifier. - required: false - schema: - type: string - - name: state - in: query - description: Filter by card state. - required: false - schema: - $ref: '#/components/schemas/CardState' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestWebhookRequest' + examples: + testWebhook: + summary: Test webhook example + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 + type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} responses: '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CardListResponse' + description: Webhook received successfully. This confirms your webhook endpoint is properly configured. '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - /cards/{id}: - parameters: - - name: id - in: path - description: System-generated unique card identifier - required: true - schema: - type: string - get: - summary: Get a card - description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. - operationId: getCardById + $ref: '#/components/schemas/Error409' + bulk-upload: + post: + summary: Bulk upload status webhook + description: | + Webhook that is called when a bulk customer upload job completes or fails. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. + operationId: bulkUploadWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BulkUploadWebhook' + examples: + completedUpload: + summary: Successful bulk upload completion + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + progress: + total: 5000 + processed: 5000 + successful: 5000 + failed: 0 + errors: [] + failedUpload: + summary: Failed bulk upload + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: FAILED + progress: + total: 5000 + processed: 5000 + successful: 0 + failed: 5000 + errors: + - correlationId: row_1 + error: + code: invalid_csv_format + message: Invalid CSV format + details: + reason: missing_required_column + column: umaAddress responses: '200': - description: Successful operation + description: Webhook received successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error501' - patch: - summary: Update a card + $ref: '#/components/schemas/Error409' + invitation-claimed: + post: + summary: Invitation claimed webhook description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - - - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - - Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): + Webhook that is called when an invitation is claimed by a customer. + This endpoint should be implemented by platform clients of the Grid API. - 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + When a customer claims an invitation, this webhook is triggered to notify the platform that: + 1. The invitation has been successfully claimed + 2. The invitee UMA address is now associated with the invitation + 3. The invitation status has changed from PENDING to CLAIMED - 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + This allows platforms to: + - Track invitation usage and conversion rates + - Trigger onboarding flows for new customers who joined via invitation + - Apply referral bonuses or rewards to the inviter + - Update their UI to reflect the claimed status - Effects: - - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - - `state: ACTIVE`: normal authorization behavior resumes. - - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. - operationId: updateCardById + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: invitationClaimedWebhook tags: - - Cards + - Webhooks security: - - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardUpdateRequest' + $ref: '#/components/schemas/InvitationClaimedWebhook' examples: - freeze: - summary: Freeze an active card - value: - state: FROZEN - unfreeze: - summary: Unfreeze a frozen card - value: - state: ACTIVE - updateFundingSources: - summary: Replace the card's bound funding sources - value: - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - freezeAndUpdateSources: - summary: Freeze the card and replace its funding sources in one call - value: - state: FROZEN - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - close: - summary: Permanently close the card + claimedInvitation: + summary: Invitation claimed notification value: - state: CLOSED + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: + code: 019542f5 + createdAt: '2025-09-01T14:30:00Z' + claimedAt: '2025-09-01T15:45:00Z' + inviterUma: $inviter@uma.domain + inviteeUma: $invitee@uma.domain + status: CLAIMED + url: https://uma.me/i/019542f5 responses: '200': - description: Signed retry accepted. Returns the updated card. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '202': - description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/SignedRequestChallenge' + description: Webhook received successfully '400': - description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /cards/{id}/reveal: - parameters: - - name: id - in: path - description: System-generated unique card identifier - required: true - schema: - type: string + customer-update: post: - summary: Reveal card details - description: |- - Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. + summary: Customer status change + description: | + Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. + This endpoint should be implemented by clients of the Grid API. - Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Every reveal is audit-logged with the requesting actor. - operationId: revealCard - tags: - - Cards - security: - - BasicAuth: [] - responses: - '200': - description: Reveal URL minted. - content: - application/json: - schema: - $ref: '#/components/schemas/CardRevealResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /sandbox/cards/{id}/simulate/authorization: - post: - summary: Simulate a card authorization - description: | - Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - - The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - - | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorization + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: customerStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate an authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + $ref: '#/components/schemas/CustomerWebhook' examples: - coffeeAuth: - summary: Approved $12.50 auth at a coffee shop + kycApprovedWebhook: + summary: When an individual customer KYC has been approved value: - amount: 1250 - currency: - code: USD - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - declinedInsufficientFunds: - summary: Declined — insufficient funds (descriptor suffix `002`) + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: San Francisco + state: CA + postalCode: '94105' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When an individual customer KYC has been rejected value: - amount: 50000 - currency: - code: USD - merchant: - descriptor: AMAZON RETAIL US-002 - mcc: '5942' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybApprovedWebhook: + summary: When a business customer KYB has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: CUSTOMER.KYB_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000003 + platformCustomerId: 7a2f9d4e1b8c3f5 + customerType: BUSINESS + region: US + currencies: + - USD + - USDC + umaAddress: $acme.corp@uma.domain.com + kybStatus: APPROVED + address: + line1: 456 Business Ave + city: New York + state: NY + postalCode: '10001' + country: US + businessInfo: + legalName: Acme Corporation + registrationNumber: '12345678' + taxId: 98-7654321 + incorporatedOn: '2018-03-14' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybRejectedWebhook: + summary: When a business customer KYB has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000010 + type: CUSTOMER.KYB_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000004 + platformCustomerId: 3c8e5f2a9d1b7e4 + customerType: BUSINESS + region: US + currencies: + - USD + umaAddress: $globex.inc@uma.domain.com + kybStatus: REJECTED + address: + line1: 789 Corporate Blvd + city: Chicago + state: IL + postalCode: '60601' + country: US + businessInfo: + legalName: Globex Inc + taxId: 47-1234567 + incorporatedOn: '2015-09-01' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/clearing: + $ref: '#/components/schemas/Error409' + internal-account-status: post: - summary: Simulate a card clearing + summary: Internal account status webhook description: | - Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. + Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. + This endpoint should be implemented by clients of the Grid API. - - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). - - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. - - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Production returns `404` on this path. - operationId: sandboxSimulateCardClearing + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Event types + - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. + - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. + operationId: internalAccountStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the clearing applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardClearingRequest' + $ref: '#/components/schemas/InternalAccountStatusWebhook' examples: - tipOnTopClearing: - summary: Clearing larger than auth — exercises post-hoc pull + balanceDecrease: + summary: A transaction just cleared a customer account and the balance has decreased value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 - authorizationExpiry: - summary: Clearing of 0 — exercises authorization expiry + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INTERNAL_ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: ACTIVE + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12500 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + statusUpdated: + summary: The status of an internal account changed (e.g., frozen by Grid) value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 0 + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INTERNAL_ACCOUNT.STATUS_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: FROZEN + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return: + $ref: '#/components/schemas/Error409' + verification-update: post: - summary: Simulate a card return + summary: Verification status change description: | - Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). + Webhook that is called when a customer's KYC/KYB verification status changes. + This endpoint should be implemented by clients of the Grid API. - Production returns `404` on this path. - operationId: sandboxSimulateCardReturn + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: verificationStatusWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the return applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardReturnRequest' + $ref: '#/components/schemas/VerificationWebhook' examples: - fullRefund: - summary: Full refund of a $15.00 settled transaction + approved: + summary: Verification approved value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: VERIFICATION.APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: APPROVED + errors: [] + createdAt: '2025-08-15T14:00:00Z' + resolveErrors: + summary: Verification requires action + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000031 + type: VERIFICATION.RESOLVE_ERRORS + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000011 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + createdAt: '2025-08-15T14:00:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/balance_inquiry: + $ref: '#/components/schemas/Error409' + card-state-change: post: - summary: Simulate a card balance inquiry + summary: Card state change description: | - Simulate a balance-inquiry authorization against a card in the sandbox environment. A balance inquiry is always a `0`-amount authorization, so the request carries no `amount` — only the `merchant`. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - Production returns `404` on this path. - operationId: sandboxSimulateCardBalanceInquiry + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardStateChangeWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a balance inquiry against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardBalanceInquiryRequest' + $ref: '#/components/schemas/CardStateChangeWebhook' examples: - balanceInquiry: - summary: Balance inquiry at a fuel pump + activated: + summary: Card transitioned from PROCESSING to ACTIVE value: - merchant: - descriptor: SHELL OIL 12345678 - mcc: '5541' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/credit_authorization: - post: - summary: Simulate a card credit authorization - description: | - Simulate an inbound credit authorization (a merchant-initiated credit to the card, e.g. a refund pushed as an authorization) in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - - Production returns `404` on this path. - operationId: sandboxSimulateCardCreditAuthorization - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a credit authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - creditAuth: - summary: $25.00 credit authorization + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + processorRef: card_b81c2a4f + issuerRef: lead_card_7a1b9c3d + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:11:00Z' + issuerRejected: + summary: Card rejected by issuer during provisioning value: - amount: 2500 - currency: - code: USD - merchant: - descriptor: ACME MARKETPLACE - mcc: '5942' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000021 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:12:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000011 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: CLOSED + stateReason: ISSUER_REJECTED + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:12:00Z' + frozen: + summary: Card frozen by the platform + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000022 + type: CARD.STATE_CHANGE + timestamp: '2026-05-09T09:00:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: FROZEN + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-09T09:00:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/financial_authorization: + $ref: '#/components/schemas/Error409' + card-funding-source-change: post: - summary: Simulate a card financial authorization + summary: Card funding source change description: | - Simulate a single-message financial authorization (an authorization that clears in the same message, e.g. an ATM withdrawal or other dual-message-exempt flow) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + This endpoint should be implemented by clients of the Grid API. - Production returns `404` on this path. - operationId: sandboxSimulateCardFinancialAuthorization - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a financial authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - financialAuth: - summary: $80.00 single-message ATM withdrawal - value: - amount: 8000 - currency: - code: USD - merchant: - descriptor: ATM WITHDRAWAL 24TH ST - mcc: '6011' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/financial_credit_authorization: - post: - summary: Simulate a card financial credit authorization - description: | - Simulate a single-message financial credit authorization (a credit to the card that clears in the same message, e.g. an ATM deposit or push credit) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + ### Authentication - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash - Production returns `404` on this path. - operationId: sandboxSimulateCardFinancialCreditAuthorization + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardFundingSourceChangeWebhook tags: - - Sandbox + - Webhooks security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card to simulate a financial credit authorization against. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + - WebhookSignature: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + $ref: '#/components/schemas/CardFundingSourceChangeWebhook' examples: - financialCreditAuth: - summary: $40.00 single-message push credit + fundingSourcesReplaced: + summary: Funding sources replaced via PATCH /cards/{id} value: - amount: 4000 - currency: - code: USD - merchant: - descriptor: ACME PAYOUTS - mcc: '6012' - country: US + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: CARD.FUNDING_SOURCE_CHANGE + timestamp: '2026-05-08T14:30:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:30:00Z' responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' + '200': + description: | + Webhook received successfully '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized - Signature validation failed content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/authorization_advice: - post: - summary: Simulate a card authorization advice - description: | - Simulate an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization on a `CardTransaction` in the sandbox environment. The `amount` is the new *total* authorized amount the advice re-sizes to. The parent transaction must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorizationAdvice - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the advice applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationAdviceRequest' - examples: - resizeUp: - summary: Re-size an open auth up to a new total of $20.00 - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 2000 - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/credit_authorization_advice: - post: - summary: Simulate a card credit authorization advice - description: | - Simulate a network-initiated `CREDIT_AUTHORIZATION_ADVICE` against a card in the sandbox environment. Like `simulate/credit_authorization`, it is keyed by the card (PAN) and `merchant` rather than an existing transaction. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - - Production returns `404` on this path. - operationId: sandboxSimulateCardCreditAuthorizationAdvice - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the advice applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - creditAdvice: - summary: $25.00 credit authorization advice - value: - amount: 2500 - currency: - code: USD - merchant: - descriptor: ACME MARKETPLACE - mcc: '5942' - country: US - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return_reversal: - post: - summary: Simulate a card return reversal - description: | - Simulate a `RETURN_REVERSAL` against an existing `REFUNDED` card transaction in the sandbox environment — reversing a previously settled return. The parent transaction must be in `REFUNDED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - - Note: authorization reversal / void has no dedicated card-issuer simulate path (the void simulator emits a distinct `VOID` event), so — like authorization expiry — it is not exposed here. - - Production returns `404` on this path. - operationId: sandboxSimulateCardReturnReversal - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the return reversal applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardTransactionRefRequest' - examples: - returnReversal: - summary: Reverse a settled return on a refunded transaction - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - responses: - '202': - description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardSimulationResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts: - post: - summary: Link a stablecoin provider account - description: | - Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. - operationId: linkStablecoinProviderAccount - tags: - - Stablecoins - security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. - required: true - schema: - type: string - maxLength: 255 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' - responses: - '201': - description: Stablecoin provider account linked - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict + '409': + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - get: - summary: List stablecoin provider account links - description: Retrieve stablecoin provider account links for the authenticated platform. - operationId: listStablecoinProviderAccounts - tags: - - Stablecoins - security: - - BasicAuth: [] - parameters: - - name: provider - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProvider' - - name: status - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProviderAccountStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts/{stablecoinProviderAccountId}: - parameters: - - name: stablecoinProviderAccountId - in: path - description: System-generated stablecoin provider account link identifier - required: true - schema: - type: string - get: - summary: Get a stablecoin provider account link - description: Retrieve a stablecoin provider account link by id for the authenticated platform. - operationId: getStablecoinProviderAccount - tags: - - Stablecoins - security: - - BasicAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin provider account link not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' -webhooks: - agent-action: - post: - summary: Agent action pending approval webhook - description: | - Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: agentActionWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionWebhook' - examples: - pendingApproval: - summary: Agent action pending approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: AGENT_ACTION.PENDING_APPROVAL - timestamp: '2025-10-03T15:00:00Z' - data: - id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 - agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 - platformCustomerId: user-a1b2c3 - status: PENDING_APPROVAL - type: EXECUTE_QUOTE - quote: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - expiresAt: '2025-10-03T15:00:30Z' - createdAt: '2025-10-03T15:00:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: INR - name: Indian Rupee - symbol: ₹ - decimals: 2 - totalSendingAmount: 50000 - totalReceivingAmount: 4625000 - exchangeRate: 92.5 - feesIncluded: 250 - platformFeesIncluded: 0 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 - createdAt: '2025-10-03T15:00:00Z' - updatedAt: '2025-10-03T15:00:00Z' - responses: - '200': - description: Webhook received and acknowledged. - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - incoming-payment: - post: - summary: Incoming payment webhook and approval mechanism - description: | - Webhook that is called when an incoming payment is received by a customer's UMA address. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Payment Approval Flow - When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: - - 1. The client should check the `counterpartyInformation` against their requirements - 2. To APPROVE the payment synchronously, return a 200 OK response - 3. To REJECT the payment, return a 403 Forbidden response with an Error object - 4. To request more information, return a 422 Unprocessable Entity with specific missing fields - 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. - - The Grid system will proceed or cancel the payment based on your response. - - For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. - operationId: incomingPaymentWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhook' - examples: - pendingPayment: - summary: Pending payment example requiring approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.PENDING - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: PENDING - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - counterpartyInformation: - FULL_NAME: John Sender - BIRTH_DATE: '1985-06-15' - NATIONALITY: US - reconciliationInstructions: - reference: REF-123456789 - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: POSTAL_ADDRESS - mandatory: false - incomingCompletedPayment: - summary: Completed payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - incomingCompletedCryptoPayment: - summary: Completed payment funded from an external crypto wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - type: INCOMING - direction: CREDIT - source: - sourceType: REALTIME_FUNDING - currency: USDC - onChainTransaction: - transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf - network: SOLANA - destination: - destinationType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC deposit from self-custody wallet - incomingRefundedPayment: - summary: Refunded incoming payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000011 - type: INCOMING_PAYMENT.REFUND_COMPLETED - timestamp: '2025-08-15T14:40:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - failureReason: OFFRAMP_FAILED - refund: - reference: REF-123456789-REFUND - initiatedAt: '2025-08-15T14:35:00Z' - settledAt: '2025-08-15T14:40:00Z' - status: COMPLETED - reason: TRANSACTION_FAILED - responses: - '200': - description: | - Webhook received successfully. - For PENDING transactions, this indicates approval to proceed with the payment. - If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookResponse' - '202': - description: | - Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in - cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. - The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: | - Forbidden - Payment rejected by the client. - Only applicable for PENDING transactions. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '422': - description: | - Unprocessable Entity - Additional counterparty information required. - Only applicable for PENDING transactions. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' - outgoing-payment: - post: - summary: Outgoing payment status webhook - description: | - Webhook that is called when an outgoing payment's status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. - operationId: outgoingPaymentWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OutgoingPaymentWebhook' - examples: - outgoingCompletedPayment: - summary: Completed outgoing payment - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivedAmount: - amount: 9706 - currency: - code: EUR - name: Euro - symbol: € - decimals: 2 - exchangeRate: 0.92 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' - paymentInstructions: [] - rateDetails: - counterpartyMultiplier: 1.08 - counterpartyFixedFee: 10 - gridApiMultiplier: 0.925 - gridApiFixedFee: 10 - gridApiVariableFeeRate: 0.003 - gridApiVariableFeeAmount: 30 - outgoingCompletedCryptoPayment: - summary: Completed crypto payout to an external wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000009 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 - onChainTransaction: - transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx - network: SOLANA - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - sentAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC withdrawal to self-custody wallet - paymentInstructions: [] - failedPayment: - summary: Failed outgoing payment - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - failureReason: QUOTE_EXECUTION_FAILED - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - test-webhook: - post: - summary: Test webhook for integration verification - description: | - Webhook that is sent once to verify your webhook endpoint is correctly set up. - This is sent when you configure or update your platform settings with a webhook URL. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is purely for testing your endpoint integration and signature verification. - operationId: testWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestWebhookRequest' - examples: - testWebhook: - summary: Test webhook example - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000001 - type: TEST - timestamp: '2025-08-15T14:32:00Z' - data: {} - responses: - '200': - description: Webhook received successfully. This confirms your webhook endpoint is properly configured. - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - bulk-upload: - post: - summary: Bulk upload status webhook - description: | - Webhook that is called when a bulk customer upload job completes or fails. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. - operationId: bulkUploadWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BulkUploadWebhook' - examples: - completedUpload: - summary: Successful bulk upload completion - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - progress: - total: 5000 - processed: 5000 - successful: 5000 - failed: 0 - errors: [] - failedUpload: - summary: Failed bulk upload - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: FAILED - progress: - total: 5000 - processed: 5000 - successful: 0 - failed: 5000 - errors: - - correlationId: row_1 - error: - code: invalid_csv_format - message: Invalid CSV format - details: - reason: missing_required_column - column: umaAddress - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - invitation-claimed: - post: - summary: Invitation claimed webhook - description: | - Webhook that is called when an invitation is claimed by a customer. - This endpoint should be implemented by platform clients of the Grid API. - - When a customer claims an invitation, this webhook is triggered to notify the platform that: - 1. The invitation has been successfully claimed - 2. The invitee UMA address is now associated with the invitation - 3. The invitation status has changed from PENDING to CLAIMED - - This allows platforms to: - - Track invitation usage and conversion rates - - Trigger onboarding flows for new customers who joined via invitation - - Apply referral bonuses or rewards to the inviter - - Update their UI to reflect the claimed status - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: invitationClaimedWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/InvitationClaimedWebhook' - examples: - claimedInvitation: - summary: Invitation claimed notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION.CLAIMED - timestamp: '2025-09-01T15:45:00Z' - data: - code: 019542f5 - createdAt: '2025-09-01T14:30:00Z' - claimedAt: '2025-09-01T15:45:00Z' - inviterUma: $inviter@uma.domain - inviteeUma: $invitee@uma.domain - status: CLAIMED - url: https://uma.me/i/019542f5 - responses: - '200': - description: Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - customer-update: - post: - summary: Customer status change - description: | - Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: customerStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CustomerWebhook' - examples: - kycApprovedWebhook: - summary: When an individual customer KYC has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: CUSTOMER.KYC_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 9f84e0c2a72c4fa - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $john.doe@uma.domain.com - kycStatus: APPROVED - fullName: John Michael Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - line2: Apt 4B - city: Seattle - state: WA - postalCode: '98101' - country: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-07-21T17:32:28Z' - isDeleted: false - kycRejectedWebhook: - summary: When an individual customer KYC has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: CUSTOMER.KYC_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000002 - platformCustomerId: 4b7c1e9d3f5a8e2 - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $jane.smith@uma.domain.com - kycStatus: REJECTED - fullName: Jane Smith - birthDate: '1988-03-22' - nationality: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybApprovedWebhook: - summary: When a business customer KYB has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: CUSTOMER.KYB_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000003 - platformCustomerId: 7a2f9d4e1b8c3f5 - customerType: BUSINESS - region: US - currencies: - - USD - - USDC - umaAddress: $acme.corp@uma.domain.com - kybStatus: APPROVED - address: - line1: 456 Business Ave - city: Seattle - state: WA - postalCode: '98101' - country: US - businessInfo: - legalName: Acme Corporation - registrationNumber: '12345678' - taxId: 98-7654321 - incorporatedOn: '2018-03-14' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybRejectedWebhook: - summary: When a business customer KYB has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000010 - type: CUSTOMER.KYB_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000004 - platformCustomerId: 3c8e5f2a9d1b7e4 - customerType: BUSINESS - region: US - currencies: - - USD - umaAddress: $globex.inc@uma.domain.com - kybStatus: REJECTED - address: - line1: 789 Corporate Blvd - city: Chicago - state: IL - postalCode: '60601' - country: US - businessInfo: - legalName: Globex Inc - taxId: 47-1234567 - incorporatedOn: '2015-09-01' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - internal-account-status: - post: - summary: Internal account status webhook - description: | - Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Event types - - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. - - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. - operationId: internalAccountStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountStatusWebhook' - examples: - balanceDecrease: - summary: A transaction just cleared a customer account and the balance has decreased - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INTERNAL_ACCOUNT.BALANCE_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: ACTIVE - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12500 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - statusUpdated: - summary: The status of an internal account changed (e.g., frozen by Grid) - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INTERNAL_ACCOUNT.STATUS_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: FROZEN - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - verification-update: - post: - summary: Verification status change - description: | - Webhook that is called when a customer's KYC/KYB verification status changes. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: verificationStatusWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/VerificationWebhook' - examples: - approved: - summary: Verification approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: VERIFICATION.APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: APPROVED - errors: [] - createdAt: '2025-08-15T14:00:00Z' - resolveErrors: - summary: Verification requires action - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000031 - type: VERIFICATION.RESOLVE_ERRORS - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000011 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - createdAt: '2025-08-15T14:00:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-state-change: - post: - summary: Card state change - description: | - Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardStateChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardStateChangeWebhook' - examples: - activated: - summary: Card transitioned from PROCESSING to ACTIVE - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:11:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - processorRef: card_b81c2a4f - issuerRef: lead_card_7a1b9c3d - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:11:00Z' - issuerRejected: - summary: Card rejected by issuer during provisioning - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000021 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:12:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000011 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: CLOSED - stateReason: ISSUER_REJECTED - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:12:00Z' - frozen: - summary: Card frozen by the platform - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000022 - type: CARD.STATE_CHANGE - timestamp: '2026-05-09T09:00:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: FROZEN - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-09T09:00:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-funding-source-change: - post: - summary: Card funding source change - description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' - examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - card-transaction: - post: - summary: Card transaction - description: | - Webhook that is called on every state transition of a card `CardTransaction`. Fires when an authorization is approved (`CARD_TRANSACTION.AUTHORIZED`), as clearings settle against it (`CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`), when settled funds are returned (`CARD_TRANSACTION.REFUNDED`), and when a pull or confirmation fails (`CARD_TRANSACTION.EXCEPTION`). The payload carries the full `CardTransaction` resource. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardTransactionWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardTransactionWebhook' - examples: - authorized: - summary: Authorization approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000040 - type: CARD_TRANSACTION.AUTHORIZED - timestamp: '2026-05-09T10:00:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: AUTHORIZED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 1 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 0 - totalAmount: 0 - authorizedAt: '2026-05-09T10:00:00Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-09T10:00:00Z' - settled: - summary: All clearings posted - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000041 - type: CARD_TRANSACTION.SETTLED - timestamp: '2026-05-09T15:42:11Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: SETTLED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 0 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 1 - totalAmount: 12550 - authorizedAt: '2026-05-09T10:00:00Z' - lastEventAt: '2026-05-09T15:42:11Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-09T15:42:11Z' - refunded: - summary: Settled funds returned by the merchant - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000042 - type: CARD_TRANSACTION.REFUNDED - timestamp: '2026-05-10T09:15:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_b81c2a4f - status: REFUNDED - direction: DEBIT - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - authorizedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - refundedAmount: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 12550 - pendingCount: 0 - refundSummary: - count: 1 - totalAmount: 12550 - settlementSummary: - count: 1 - totalAmount: 12550 - authorizedAt: '2026-05-09T10:00:00Z' - lastEventAt: '2026-05-10T09:15:00Z' - createdAt: '2026-05-09T10:00:00Z' - updatedAt: '2026-05-10T09:15:00Z' - exception: - summary: Settled to the network but the funding-source pull failed - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000043 - type: CARD_TRANSACTION.EXCEPTION - timestamp: '2026-05-09T16:05:00Z' - data: - type: CARD - id: CardTransaction:019542f5-b3e7-1d02-0000-000000000101 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - issuerTransactionToken: lithic_txn_c92d3b5a - status: EXCEPTION - direction: DEBIT - merchant: - descriptor: UBER EATS - mcc: '5812' - country: US - authorizedAmount: - amount: 4200 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAmount: - amount: 4200 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - pullSummary: - count: 1 - totalAmount: 0 - pendingCount: 0 - refundSummary: - count: 0 - totalAmount: 0 - settlementSummary: - count: 1 - totalAmount: 4200 - authorizedAt: '2026-05-09T15:30:00Z' - lastEventAt: '2026-05-09T16:05:00Z' - createdAt: '2026-05-09T15:30:00Z' - updatedAt: '2026-05-09T16:05:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' -components: - securitySchemes: - BasicAuth: - type: http - scheme: basic - description: API token authentication using format `:` - AgentAuth: - type: http - scheme: bearer - description: 'Bearer token authentication for agent-scoped endpoints. The token is the `accessToken` returned when redeeming a device code via `POST /agents/device-codes/{code}/redeem`. Agent credentials are user-scoped: all requests are automatically bound to the agent''s associated customer and subject to the agent''s policy.' - WebhookSignature: - type: apiKey - in: header - name: X-Grid-Signature - description: | - Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - schemas: - CustomerInfoFieldName: - type: string - enum: - - FULL_NAME - - BIRTH_DATE - - NATIONALITY - - PHONE_NUMBER - - EMAIL - - POSTAL_ADDRESS - - TAX_ID - - REGISTRATION_NUMBER - - USER_TYPE - - COUNTRY_OF_RESIDENCE - - ACCOUNT_IDENTIFIER - - FI_LEGAL_ENTITY_NAME - - FI_ADDRESS - - PURPOSE_OF_PAYMENT - - ULTIMATE_INSTITUTION_COUNTRY - - IDENTIFIER - - BUSINESS_TYPE - - COMPANY_LEGAL_NAME - - ID_TYPE - - ID_NUMBER - description: Name of a type of field containing info about a platform's customer or counterparty customer. - example: FULL_NAME - CounterpartyFieldDefinition: - type: object - properties: - name: - $ref: '#/components/schemas/CustomerInfoFieldName' - mandatory: - type: boolean - description: Whether the field is mandatory - example: true - required: - - name - - mandatory - TransactionType: - type: string - enum: - - INCOMING - - OUTGOING - description: Type of transaction (incoming payment or outgoing payment) - PlatformCurrencyConfig: - type: object - properties: - currencyCode: - type: string - description: Three-letter currency code (ISO 4217) - example: USD - minAmount: - type: integer - format: int64 - description: Minimum amount that can be sent in the smallest unit of this currency - minimum: 0 - example: 100 - maxAmount: - type: integer - format: int64 - description: Maximum amount that can be sent in the smallest unit of this currency - minimum: 0 - example: 1000000 - requiredCounterpartyFields: - type: array - items: - $ref: '#/components/schemas/CounterpartyFieldDefinition' - description: List of fields which the platform requires from the counterparty institutions about counterparty customers. Platforms can set mandatory to false if the platform does not require the field, but would like to have it available. Some fields may be required by the underlying UMA provider. - example: - - name: FULL_NAME - mandatory: true - - name: BIRTH_DATE - mandatory: true - - name: NATIONALITY - mandatory: true - providerRequiredCustomerFields: - type: array - items: - $ref: '#/components/schemas/CustomerInfoFieldName' - description: List of customer info field names that are required by the underlying UMA provider when creating a customer for this currency. These fields must be supplied when creating or updating a customer if this currency is intended to be used by that customer. If no fields are required, this field is omitted. - readOnly: true - example: - - NATIONALITY - - BIRTH_DATE - providerRequiredCounterpartyCustomerFields: - type: array - items: - $ref: '#/components/schemas/CustomerInfoFieldName' - description: List of fields that are required by the underlying UMA provider for this currency. If the counterparty does not provide these fields, quote requests will fail. - readOnly: true - example: - - FULL_NAME - - COUNTRY_OF_RESIDENCE - enabledTransactionTypes: - type: array - items: - $ref: '#/components/schemas/TransactionType' - description: List of transaction types that are enabled for this currency. - example: - - OUTGOING - - INCOMING - required: - - currencyCode - - minAmount - - maxAmount - - requiredCounterpartyFields - - enabledTransactionTypes - EmbeddedWalletConfig: - type: object - description: | - Per-platform embedded-wallet configuration. Controls branding and OTP - behavior for the email sent when a customer authenticates with an - EMAIL_OTP credential. Fields omitted from a request are left unchanged. - properties: - appName: - type: string - maxLength: 255 - description: App name displayed in the default OTP email template. - example: Acme Wallet - otpLength: - type: integer - minimum: 4 - maximum: 12 - description: | - Number of digits / characters in the OTP code. Defaults to 6 when - not set. - example: 6 - alphanumeric: - type: boolean - description: | - If true, OTP includes letters in addition to digits. Defaults to - numeric-only when not set. - example: false - expirationSeconds: - type: integer - minimum: 1 - maximum: 86400 - description: | - OTP validity window in seconds. Defaults to 300 when not set. - example: 300 - sendFromEmailAddress: - type: string - format: email - maxLength: 255 - description: Custom sender email address for OTP emails. - example: noreply@acme.com - sendFromEmailSenderName: - type: string - maxLength: 255 - description: | - Custom sender display name. Defaults to "Notifications" when not set. - example: Acme Notifications - replyToEmailAddress: - type: string - format: email - maxLength: 255 - description: Custom reply-to email address for OTP emails. - example: support@acme.com - logoUrl: - type: string - format: uri - maxLength: 512 - description: URL to a PNG logo for the OTP email. Resized to 340x124px. - example: https://acme.com/logo.png - CardTokenization2FAEmailConfig: - type: object - description: Email branding and sender settings for card-tokenization authentication messages. Invalid or unverified sender identities can cause delivery to fail. - properties: - fromAddress: - type: string - format: email - maxLength: 255 - description: Sender address for card-tokenization authentication emails. - example: cards@acme.com - fromName: - type: string - maxLength: 255 - description: Sender display name. - example: Acme Cards - replyToAddress: - type: string - format: email - maxLength: 255 - description: Reply-to address for card-tokenization authentication emails. - example: support@acme.com - subject: - type: string - maxLength: 255 - description: Subject for the authentication email. - example: Your Acme card verification code - bodyText: - type: string - maxLength: 2000 - description: | - Plain-text message content. Lightspark inserts the authentication code into - a controlled text and HTML template; arbitrary HTML and template variables - are not supported. - example: Use this code to finish adding your Acme card to your digital wallet. - CardTokenization2FASmsConfig: - type: object - description: SMS settings for card-tokenization authentication messages delivered through a Lightspark-managed Twilio sender. - properties: - templateSid: - type: string - maxLength: 64 - description: | - Twilio Verify template SID to use for this platform. An invalid or unavailable - template can cause delivery to fail. - example: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - bodyText: - type: string - maxLength: 480 - description: | - Plain-text fallback message used when Twilio Verify is unavailable for the - authentication code. Lightspark appends the code to this text. - example: Use this code to finish adding your Acme card to your digital wallet. - CardTokenization2FAConfig: - type: object - description: Per-platform branding for authentication codes sent when a cardholder adds a Grid-issued card to a digital wallet. Updates apply to subsequent delivery attempts. Invalid sender or provider configuration can cause delivery to fail. - properties: - displayName: - type: string - maxLength: 255 - description: Platform name displayed in authentication messages. - example: Acme - logoUrl: - type: string - format: uri - pattern: ^https:// - maxLength: 512 - description: HTTPS URL of the logo displayed in email messages. - example: https://acme.com/card-email-logo.png - email: - $ref: '#/components/schemas/CardTokenization2FAEmailConfig' - sms: - $ref: '#/components/schemas/CardTokenization2FASmsConfig' - FeeType: - type: string - enum: - - CROSS_CURRENCY_TRANSACTION - description: | - The kind of activity this fee applies to. - - - `CROSS_CURRENCY_TRANSACTION` — fee charged on a cross-currency Grid transaction - (source currency differs from destination currency). - example: CROSS_CURRENCY_TRANSACTION - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 - CurrencyAmount: - type: object - required: - - amount - - currency - properties: - amount: - type: integer - format: int64 - description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) - example: 12550 - currency: - $ref: '#/components/schemas/Currency' - FeeConfig: - type: object - description: A platform-configured fee collected by Grid and settled to the platform internal account. There can be at most one fee config for a given fee type and source currency pair. The fee will apply to all transactions of the fee type that originate in the source currency. - properties: - feeType: - $ref: '#/components/schemas/FeeType' - sourceCurrency: - type: string - description: Currency code of the sending side this fee applies to. Only `USD` is accepted today; other currencies return a `NOT_IMPLEMENTED` error. - example: USD - variableFeeBps: - type: integer - minimum: 0 - maximum: 10000 - description: Variable fee in basis points (1 bps = 0.01%) to apply to a transaction's source-currency amount. - example: 30 - fixedFee: - $ref: '#/components/schemas/CurrencyAmount' - description: Fixed fee charged per transaction, in the smallest unit of the source currency. The fixed fee currency must match the fee config's `sourceCurrency`. - required: - - feeType - - sourceCurrency - - variableFeeBps - - fixedFee - PlatformConfig: - type: object - properties: - id: - type: string - description: System-generated unique identifier - readOnly: true - example: PlatformConfig:019542f5-b3e7-1d02-0000-000000000003 - umaDomain: - type: string - description: UMA domain for this platform - example: platform.uma.domain - proxyUmaSubdomain: - type: string - description: The subdomain that incoming requests will be proxied to - example: platform - webhookEndpoint: - type: string - description: URL where webhook notifications will be sent - example: https://api.mycompany.com/webhooks/uma - supportedCurrencies: - type: array - items: - $ref: '#/components/schemas/PlatformCurrencyConfig' - description: | - List of currencies supported by the platform. This is what the platform's - customers are able to hold, send, and receive. - isRegulatedFinancialInstitution: - type: boolean - description: | - Whether the platform is a regulated financial institution. This is used to - determine if the platform's customers must be KYC/KYB'd by Lightspark via - the KYC link flow. This can only be set by Lightspark during platform - creation. - example: false - embeddedWalletConfig: - $ref: '#/components/schemas/EmbeddedWalletConfig' - description: | - Embedded-wallet branding and OTP settings for this platform. Present - only when the platform has configured embedded-wallet support; - omitted otherwise. - cardTokenization2faConfig: - $ref: '#/components/schemas/CardTokenization2FAConfig' - description: | - Branding and sender configuration for card-tokenization authentication - messages. This configuration is independent of embedded-wallet support. - feeConfigs: - type: array - items: - $ref: '#/components/schemas/FeeConfig' - description: | - Platform-collected fees that should be added on top of - Grid-collected fees. Contains every currently-active fee config - for the platform. - createdAt: - type: string - format: date-time - description: Creation timestamp - readOnly: true - example: '2025-06-15T12:30:45Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - readOnly: true - example: '2025-06-15T12:30:45Z' - Error401: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 401 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | UNAUTHORIZED | Issue with API credentials | - | INVALID_SIGNATURE | Signature header is invalid | - | WALLET_SIGNATURE_MISSING | The `Grid-Wallet-Signature` header is required for this Embedded Wallet action but was not supplied | - | WALLET_SIGNATURE_MALFORMED | The `Grid-Wallet-Signature` header could not be parsed (bad encoding, structure, or fields) | - | WALLET_SIGNATURE_BODY_MISMATCH | The `Grid-Wallet-Signature` was computed over a different request body than the one received | - | WALLET_SIGNATURE_INVALID | The `Grid-Wallet-Signature` failed cryptographic verification against the registered credential | - | REQUEST_ID_MISSING | The `Request-Id` header is required on the signed retry but was not supplied (paired with `Grid-Wallet-Signature`) | - enum: - - UNAUTHORIZED - - INVALID_SIGNATURE - - WALLET_SIGNATURE_MISSING - - WALLET_SIGNATURE_MALFORMED - - WALLET_SIGNATURE_BODY_MISMATCH - - WALLET_SIGNATURE_INVALID - - REQUEST_ID_MISSING - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error500: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 500 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | GRID_SWITCH_ERROR | Grid switch error | - | INTERNAL_ERROR | Internal server or UMA error | - enum: - - GRID_SWITCH_ERROR - - INTERNAL_ERROR - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - PlatformConfigUpdateRequest: - type: object - properties: - umaDomain: - type: string - example: mycompany.com - webhookEndpoint: - type: string - example: https://api.mycompany.com/webhooks/uma - supportedCurrencies: - type: array - items: - $ref: '#/components/schemas/PlatformCurrencyConfig' - embeddedWalletConfig: - $ref: '#/components/schemas/EmbeddedWalletConfig' - description: | - Update or create the embedded-wallet configuration for this platform. - Fields omitted from the nested object are left unchanged. Omit this - field at the top level to leave the embedded-wallet configuration - unchanged entirely. - cardTokenization2faConfig: - $ref: '#/components/schemas/CardTokenization2FAConfig' - description: | - Update card-tokenization authentication branding and delivery settings. - Fields omitted from the nested object are left unchanged. Changes apply - to subsequent delivery attempts. - feeConfigs: - type: array - items: - $ref: '#/components/schemas/FeeConfig' - description: | - Merge-by-key upsert of platform fee configs, keyed by - `(feeType, sourceCurrency)`. Setting variable and fixed fees to 0 for - an existing fee config deactivates it. Only `sourceCurrency: USD` is - accepted today. Omit this field to leave fee configs unchanged. - FieldConstraint: - type: object - description: Machine-readable validator hint accompanying a 400 `INVALID_INPUT` error. Consumers use it to drive form UI (input types, dropdowns, masking, length limits) and to pre-validate the field client-side before re-submitting. Fields are additive. - properties: - format: - type: string - description: Named format the value must satisfy — HTML5 input type names (`email`, `tel`, `url`, `date`, ...) or semantic slugs (`iso3166-1-alpha-2`, `bcp47-language-tag`, `us-ssn`, `e.164`). - example: email - pattern: - type: string - description: Regular expression the value must match (JavaScript-flavor). - example: ^\d{5}(-\d{4})?$ - enum: - type: array - items: - type: string - description: Allowed values when the field is drawn from a fixed set. - example: - - SSN - - ITIN - - NON_US_TAX_ID - minLength: - type: integer - description: Minimum length in characters. - example: 1 - maxLength: - type: integer - description: Maximum length in characters. - example: 500 - FieldError: - type: object - required: - - field - description: One field-level validation failure. Field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`) emit an array of these under `details.errors` so platforms can render form-field-level UX for every failure in a single round-trip. - properties: - field: - type: string - description: Dot-notation path to the offending field. - example: taxIdentifier - constraint: - $ref: '#/components/schemas/FieldConstraint' - message: - type: string - description: Human-readable explanation of what's wrong with this field. - example: Value is not one of the allowed enum members. - Error400: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 400 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | INVALID_INPUT | Invalid input provided | - | MISSING_MANDATORY_USER_INFO | Required customer information is missing | - | INVITATION_ALREADY_CLAIMED | Invitation has already been claimed | - | INVITATIONS_NOT_CONFIGURED | Invitations are not configured | - | INVALID_UMA_ADDRESS | UMA address format is invalid | - | INVITATION_CANCELLED | Invitation has been cancelled | - | QUOTE_REQUEST_FAILED | An issue occurred during the quote process; this is retryable | - | INVALID_PAYREQ_RESPONSE | Counterparty Payreq response was invalid | - | INVALID_RECEIVER | Receiver is invalid | - | PARSE_PAYREQ_RESPONSE_ERROR | Error parsing receiver PayReq response | - | CERT_CHAIN_INVALID | Counterparty certificate chain is invalid | - | CERT_CHAIN_EXPIRED | Counterparty certificate chain has expired | - | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | - | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | - | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range | - | INVALID_CURRENCY | Currency is invalid | - | INVALID_TIMESTAMP | Timestamp is invalid | - | INVALID_NONCE | Nonce is invalid | - | INVALID_REQUEST_FORMAT | Request format is invalid | - | INVALID_BANK_ACCOUNT | Bank account is invalid | - | SELF_PAYMENT | Self payment not allowed | - | LOOKUP_REQUEST_FAILED | Lookup request failed | - | PARSE_LNURLP_RESPONSE_ERROR | Error parsing LNURLP response | - | INVALID_AMOUNT | Amount is invalid | - | WEBHOOK_ENDPOINT_NOT_SET | Webhook endpoint is not set | - | WEBHOOK_DELIVERY_ERROR | Webhook delivery error | - | LOW_QUALITY | Document quality too low to process | - | DATA_MISMATCH | Document details don't match provided information | - | EXPIRED | Document has expired | - | SUSPECTED_FRAUD | Document suspected of being forged or edited | - | UNSUITABLE_DOCUMENT | Document type is not accepted or not supported | - | INCOMPLETE | Document is missing pages or sides | - | EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS | An EMAIL_OTP credential is already registered on the target internal account; only one email OTP credential is supported per internal account at this time | - | SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time | - | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account | - | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | - | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | - | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - enum: - - INVALID_INPUT - - MISSING_MANDATORY_USER_INFO - - INVITATION_ALREADY_CLAIMED - - INVITATIONS_NOT_CONFIGURED - - INVALID_UMA_ADDRESS - - INVITATION_CANCELLED - - QUOTE_REQUEST_FAILED - - INVALID_PAYREQ_RESPONSE - - INVALID_RECEIVER - - PARSE_PAYREQ_RESPONSE_ERROR - - CERT_CHAIN_INVALID - - CERT_CHAIN_EXPIRED - - INVALID_PUBKEY_FORMAT - - MISSING_REQUIRED_UMA_PARAMETERS - - SENDER_NOT_ACCEPTED - - AMOUNT_OUT_OF_RANGE - - INVALID_CURRENCY - - INVALID_TIMESTAMP - - INVALID_NONCE - - INVALID_REQUEST_FORMAT - - INVALID_BANK_ACCOUNT - - SELF_PAYMENT - - LOOKUP_REQUEST_FAILED - - PARSE_LNURLP_RESPONSE_ERROR - - INVALID_AMOUNT - - WEBHOOK_ENDPOINT_NOT_SET - - WEBHOOK_DELIVERY_ERROR - - LOW_QUALITY - - DATA_MISMATCH - - EXPIRED - - SUSPECTED_FRAUD - - UNSUITABLE_DOCUMENT - - INCOMPLETE - - EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS - - SMS_OTP_CREDENTIAL_ALREADY_EXISTS - - PASSKEY_CREDENTIAL_ALREADY_EXISTS - - STABLECOIN_PROVIDER_ACCOUNT_INVALID - - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - message: - type: string - description: Error message - details: - type: object - description: Additional error details. Shape varies by `code`. For field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`), `details.errors[]` enumerates every invalid field so platforms can render form-field-level UX for the entire request in a single round-trip. - properties: - errors: - type: array - description: One entry per invalid field. Present on field-validation errors from submit endpoints. - items: - $ref: '#/components/schemas/FieldError' - additionalProperties: true - Error501: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 501 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory payee data key | - | NOT_IMPLEMENTED | Feature not implemented | - enum: - - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY - - NOT_IMPLEMENTED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - PaymentRail: - type: string - enum: - - ACH - - ACH_COLOMBIA - - BANK_TRANSFER - - BRE_B - - CIPS - - FAST - - FASTER_PAYMENTS - - FEDNOW - - INSTAPAY - - MOBILE_MONEY - - NEFT - - PAYNOW - - PESONET - - PIX - - RTGS - - RTP - - SEPA - - SEPA_INSTANT - - SPEI - - SWIFT - - UNIONPAY - - UPI - - WIRE - description: The payment rail used for the transfer. Payment rails represent the underlying payment network or system used to move funds between accounts. - example: ACH - ExchangeRateFees: - type: object - description: Fees associated with an exchange rate - properties: - fixed: - type: integer - format: int64 - description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) - minimum: 0 - example: 100 - total: - type: integer - format: int64 - description: Total fees in the smallest unit of the sending currency (e.g., cents for USD). This value may change depending on the sending amount used; if no sending amount is specified, it falls back to the default. - minimum: 0 - example: 100 - ExchangeRate: - type: object - description: Exchange rate information for a currency corridor - required: - - sourceCurrency - - destinationCurrency - - destinationPaymentRail - - minSendingAmount - - maxSendingAmount - - sendingAmount - - receivingAmount - - exchangeRate - - fees - - updatedAt - properties: - sourceCurrency: - $ref: '#/components/schemas/Currency' - sendingAmount: - type: integer - format: int64 - description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. - minimum: 0 - example: 10000 - minSendingAmount: - type: integer - format: int64 - description: The minimum supported sending amount in the smallest unit of the source currency. - minimum: 0 - example: 100 - maxSendingAmount: - type: integer - format: int64 - description: The maximum supported sending amount in the smallest unit of the source currency. - minimum: 0 - example: 10000000 - destinationCurrency: - $ref: '#/components/schemas/Currency' - destinationPaymentRail: - allOf: - - $ref: '#/components/schemas/PaymentRail' - - description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) - example: UPI - receivingAmount: - type: integer - format: int64 - description: The receiving amount in the smallest unit of the destination currency - minimum: 0 - example: 1650000 - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 0.012121 - fees: - $ref: '#/components/schemas/ExchangeRateFees' - updatedAt: - type: string - format: date-time - description: Timestamp when this exchange rate was last refreshed - example: '2025-02-05T12:00:00Z' - ExchangeRateListResponse: - type: object - required: - - data - properties: - data: - type: array - description: List of exchange rates matching the filter criteria - items: - $ref: '#/components/schemas/ExchangeRate' - DiscoveryListResponse: - type: object - properties: - data: - type: array - description: List of payment rails matching the filter criteria - items: - type: object - required: - - bankName - - displayName - - country - - currency - properties: - bankName: - type: string - description: | - Canonical name for this payment rail. Pass this value as `bankName` when creating an external account. - displayName: - type: string - description: Human-friendly display name for this payment rail. - country: - type: string - description: ISO 3166-1 alpha-2 country code. - currency: - type: string - description: ISO 4217 currency code. - CustomerType: - type: string - enum: - - INDIVIDUAL - - BUSINESS - description: Whether the customer is an individual or a business entity - example: INDIVIDUAL - ContactVerificationStatus: - type: string - description: Status of an individual contact-verification channel (email or phone). `PENDING` means verification is required but not yet completed; `VERIFIED` means the channel has been confirmed. - enum: - - PENDING - - VERIFIED - example: VERIFIED - ContactVerification: - type: object - description: |- - Email and/or phone verification state for the customer. This object is **only present when the customer's regulatory jurisdiction requires contact verification** (e.g. EU customers). For customers who have no such requirement, this object is omitted entirely — no action is needed. - - Each channel is reported independently: only the channels the customer's provider actually requires are present. A provider may require both email and phone, just one of them, or — when the object is absent — neither. Every channel that **is** present must reach `VERIFIED` before the customer can begin KYC. Drive each present channel with `POST /customers/{customerId}/verify-email` and/or `POST /customers/{customerId}/verify-phone` (and their `/confirm` sub-routes). - properties: - email: - $ref: '#/components/schemas/ContactVerificationStatus' - description: Verification status of the customer's email address. Present only when the provider requires email verification. - phone: - $ref: '#/components/schemas/ContactVerificationStatus' - description: Verification status of the customer's phone number. Present only when the provider requires phone verification. - Customer: - type: object - required: - - umaAddress - - platformCustomerId - - customerType - properties: - id: - type: string - description: System-generated unique identifier - readOnly: true - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: - type: string - description: Platform-specific customer identifier - example: 9f84e0c2a72c4fa - customerType: - $ref: '#/components/schemas/CustomerType' - region: - type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity and regulatory jurisdiction. - example: US - currencies: - type: array - items: - type: string - description: List of currency codes enabled for this customer. - example: - - USD - - USDC - email: - type: string - format: email - description: Email address for the customer. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. - example: '+14155551234' - umaAddress: - type: string - description: Full UMA address (always present in responses, even if system-generated). This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - createdAt: - type: string - format: date-time - description: Creation timestamp - readOnly: true - example: '2025-07-21T17:32:28Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - readOnly: true - example: '2025-07-21T17:32:28Z' - isDeleted: - type: boolean - description: Whether the customer is marked as deleted - example: false - readOnly: true - contactVerification: - allOf: - - $ref: '#/components/schemas/ContactVerification' - readOnly: true - description: Email and phone verification state. **Only present when the customer's payment provider requires it** (e.g. EU customers); omitted otherwise. - KycStatus: +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + description: API token authentication using format `:` + AgentAuth: + type: http + scheme: bearer + description: 'Bearer token authentication for agent-scoped endpoints. The token is the `accessToken` returned when redeeming a device code via `POST /agents/device-codes/{code}/redeem`. Agent credentials are user-scoped: all requests are automatically bound to the agent''s associated customer and subject to the agent''s policy.' + WebhookSignature: + type: apiKey + in: header + name: X-Grid-Signature + description: | + Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + schemas: + CustomerInfoFieldName: type: string enum: - - UNVERIFIED - - PENDING - - APPROVED - - REJECTED - - HOLD - description: The current KYC status of a customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. - example: APPROVED - Address: + - FULL_NAME + - BIRTH_DATE + - NATIONALITY + - PHONE_NUMBER + - EMAIL + - POSTAL_ADDRESS + - TAX_ID + - REGISTRATION_NUMBER + - USER_TYPE + - COUNTRY_OF_RESIDENCE + - ACCOUNT_IDENTIFIER + - FI_LEGAL_ENTITY_NAME + - FI_ADDRESS + - PURPOSE_OF_PAYMENT + - ULTIMATE_INSTITUTION_COUNTRY + - IDENTIFIER + - BUSINESS_TYPE + - COMPANY_LEGAL_NAME + - ID_TYPE + - ID_NUMBER + description: Name of a type of field containing info about a platform's customer or counterparty customer. + example: FULL_NAME + CounterpartyFieldDefinition: type: object - required: - - line1 - - postalCode - - country properties: - line1: - type: string - description: Street address line 1 - example: 123 Main Street - line2: - type: string - description: Street address line 2 - example: Apt 4B - city: - type: string - description: City - example: San Francisco - state: - type: string - description: State/Province/Region - example: CA - postalCode: - type: string - description: Postal/ZIP code - example: '94105' - country: - type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - IdentificationType: - type: string - enum: - - SSN - - ITIN - - EIN - - NON_US_TAX_ID - description: Type of tax identification - example: SSN - IndividualSourceOfFundsCategory: - type: string - enum: - - SALARY - - SELF_EMPLOYMENT_INCOME - - INVESTMENT_INCOME - - PENSION - - RENTAL_INCOME - - GIFT - - INHERITANCE - - LOAN - - SAVINGS - - SALE_OF_ASSETS - - OTHER - description: A structured source-of-funds category for an individual customer (FLOW of funds into this account). Distinct from `SourceOfFundsCategory`, which covers business customers. - example: SALARY - SourceOfWealthCategory: - type: string - enum: - - SALARY - - BUSINESS_INCOME - - INVESTMENTS - - INHERITANCE - - PROPERTY_SALE - - GIFT - - RETIREMENT - - SAVINGS - - OTHER - description: Origin of the customer's accumulated wealth (STOCK). Distinct from `SourceOfFundsCategory` which describes the FLOW of funds for this specific account. - example: SALARY - PurposeOfAccount: - type: string - enum: - - CONTRACTOR_PAYOUTS - - CREATOR_PAYOUTS - - EMPLOYEE_PAYOUTS - - MARKETPLACE_SELLER_PAYOUTS - - SUPPLIER_PAYMENTS - - CROSS_BORDER_B2B - - AR_AUTOMATION - - AP_AUTOMATION - - EMBEDDED_PAYMENTS - - PLATFORM_FEE_COLLECTION - - P2P_TRANSFERS - - CHARITABLE_DONATIONS - - OTHER - description: The intended purpose for using the Grid account - example: CONTRACTOR_PAYOUTS - ExpectedMonthlyTransactionCount: - type: string - enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - ExpectedMonthlyTransactionVolume: - type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - AnnualIncomeRange: - type: string - enum: - - UNDER_50K - - RANGE_50K_100K - - RANGE_100K_250K - - RANGE_250K_1M - - OVER_1M - description: Bucketed annual income (USD equivalent). Used for enhanced due diligence on higher-risk profiles. - example: RANGE_100K_250K - NetWorthRange: - type: string - enum: - - UNDER_100K - - RANGE_100K_500K - - RANGE_500K_1M - - RANGE_1M_5M - - RANGE_5M_25M - - OVER_25M - description: Bucketed total net worth (USD equivalent). Used for enhanced due diligence on higher-risk profiles. - example: RANGE_500K_1M - PepStatus: + name: + $ref: '#/components/schemas/CustomerInfoFieldName' + mandatory: + type: boolean + description: Whether the field is mandatory + example: true + required: + - name + - mandatory + TransactionType: type: string enum: - - NONE - - DOMESTIC - - FOREIGN - - HIO - - FAMILY_OR_ASSOCIATE - description: Political exposure declaration (Politically Exposed Person status). `HIO` = head of an international organization. `FAMILY_OR_ASSOCIATE` covers close family members and known close associates of a PEP. - example: NONE - CustomerEdd: + - INCOMING + - OUTGOING + description: Type of transaction (incoming payment or outgoing payment) + PlatformCurrencyConfig: type: object - description: Enhanced-due-diligence (EDD) fields available as optional patchable attributes on an individual customer. Referenced via `allOf` from `IndividualCustomerFields`, so these appear as top-level optional fields on the customer resource itself; there is no separate EDD resource. The specific set required for a given customer is driven by the KYC provider's per-jurisdiction / per-flow / per-volume-tier rules (surfaced through `MISSING_FIELD` errors on `POST /verifications`). properties: - sourceOfFundsCategories: + currencyCode: + type: string + description: Three-letter currency code (ISO 4217) + example: USD + minAmount: + type: integer + format: int64 + description: Minimum amount that can be sent in the smallest unit of this currency + minimum: 0 + example: 100 + maxAmount: + type: integer + format: int64 + description: Maximum amount that can be sent in the smallest unit of this currency + minimum: 0 + example: 1000000 + requiredCounterpartyFields: type: array items: - $ref: '#/components/schemas/IndividualSourceOfFundsCategory' - description: Structured source-of-funds categories (FLOW of funds for this account). + $ref: '#/components/schemas/CounterpartyFieldDefinition' + description: List of fields which the platform requires from the counterparty institutions about counterparty customers. Platforms can set mandatory to false if the platform does not require the field, but would like to have it available. Some fields may be required by the underlying UMA provider. example: - - SALARY - sourceOfFundsOtherDescription: - type: string - description: Free-form description of the customer's source of funds. Required when `sourceOfFundsCategories` includes `OTHER`; otherwise omitted. - example: Contest winnings - sourceOfWealthCategories: + - name: FULL_NAME + mandatory: true + - name: BIRTH_DATE + mandatory: true + - name: NATIONALITY + mandatory: true + providerRequiredCustomerFields: type: array items: - $ref: '#/components/schemas/SourceOfWealthCategory' - description: Structured source-of-wealth categories (STOCK — origin of accumulated wealth). - example: - - SALARY - - INVESTMENTS - sourceOfWealthOtherDescription: - type: string - description: Free-form description of the customer's source of wealth. Required when `sourceOfWealthCategories` includes `OTHER`; otherwise omitted. - example: Royalty income from published works - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - purposeOfAccountOtherDescription: - type: string - description: Free-form description of the customer's intended purpose for the Grid account. Required when `purposeOfAccount` is `OTHER`; otherwise omitted. - example: Household budgeting between spouses - expectedMonthlyTransactionCount: - $ref: '#/components/schemas/ExpectedMonthlyTransactionCount' - expectedMonthlyTransactionVolume: - $ref: '#/components/schemas/ExpectedMonthlyTransactionVolume' - annualIncomeRange: - $ref: '#/components/schemas/AnnualIncomeRange' - netWorthRange: - $ref: '#/components/schemas/NetWorthRange' - pepStatus: - $ref: '#/components/schemas/PepStatus' - IndividualCustomerFields: - allOf: - - type: object - required: - - customerType - properties: - customerType: - type: string - enum: - - INDIVIDUAL - kycStatus: - $ref: '#/components/schemas/KycStatus' - fullName: - type: string - description: Individual's full name - example: John Michael Doe - birthDate: - type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1990-01-15' - nationality: - type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - address: - $ref: '#/components/schemas/Address' - taxIdType: - $ref: '#/components/schemas/IdentificationType' - taxIdentifier: - type: string - description: Tax-identification number. For US persons this is the SSN (format `###-##-####`) or ITIN. For non-US persons this is the tax number issued by `taxIdCountryOfIssuance`. - example: 123-45-6789 - taxIdCountryOfIssuance: - type: string - description: Country that issued the tax identifier (ISO 3166-1 alpha-2). Required when `taxIdType` is `NON_US_TAX_ID`. - example: US - - $ref: '#/components/schemas/CustomerEdd' - IndividualCustomer: - title: Individual Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/IndividualCustomerFields' - KybStatus: - type: string - enum: - - UNVERIFIED - - PENDING - - APPROVED - - REJECTED - - HOLD - description: The current KYB status of a business customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. - example: APPROVED - EntityType: - type: string - enum: - - SOLE_PROPRIETORSHIP - - PARTNERSHIP - - LLC - - CORPORATION - - S_CORPORATION - - NON_PROFIT - - PUBLICLY_LISTED_COMPANY - - TRUST - - PRIVATE_FOUNDATION - - CHARITY - - OTHER - description: Legal entity type of the business - example: LLC - BusinessType: - type: string - description: The high-level industry category of the business - enum: - - AGRICULTURE_FORESTRY_FISHING_AND_HUNTING - - MINING_QUARRYING_AND_OIL_AND_GAS_EXTRACTION - - UTILITIES - - CONSTRUCTION - - MANUFACTURING - - WHOLESALE_TRADE - - RETAIL_TRADE - - TRANSPORTATION_AND_WAREHOUSING - - INFORMATION - - FINANCE_AND_INSURANCE - - REAL_ESTATE_AND_RENTAL_AND_LEASING - - PROFESSIONAL_SCIENTIFIC_AND_TECHNICAL_SERVICES - - MANAGEMENT_OF_COMPANIES_AND_ENTERPRISES - - ADMINISTRATIVE_AND_SUPPORT_AND_WASTE_MANAGEMENT_AND_REMEDIATION_SERVICES - - EDUCATIONAL_SERVICES - - HEALTH_CARE_AND_SOCIAL_ASSISTANCE - - ARTS_ENTERTAINMENT_AND_RECREATION - - ACCOMMODATION_AND_FOOD_SERVICES - - OTHER_SERVICES - - PUBLIC_ADMINISTRATION - SourceOfFundsCategory: - type: string - enum: - - OPERATING_REVENUE - - INVESTMENT_INCOME - - LOANS - - VENTURE_CAPITAL - - PERSONAL_SAVINGS - - DONATIONS - - OTHER - description: A structured source-of-funds category for the business - example: OPERATING_REVENUE - BusinessInfoUpdate: + $ref: '#/components/schemas/CustomerInfoFieldName' + description: List of customer info field names that are required by the underlying UMA provider when creating a customer for this currency. These fields must be supplied when creating or updating a customer if this currency is intended to be used by that customer. If no fields are required, this field is omitted. + readOnly: true + example: + - NATIONALITY + - BIRTH_DATE + providerRequiredCounterpartyCustomerFields: + type: array + items: + $ref: '#/components/schemas/CustomerInfoFieldName' + description: List of fields that are required by the underlying UMA provider for this currency. If the counterparty does not provide these fields, quote requests will fail. + readOnly: true + example: + - FULL_NAME + - COUNTRY_OF_RESIDENCE + enabledTransactionTypes: + type: array + items: + $ref: '#/components/schemas/TransactionType' + description: List of transaction types that are enabled for this currency. + example: + - OUTGOING + - INCOMING + required: + - currencyCode + - minAmount + - maxAmount + - requiredCounterpartyFields + - enabledTransactionTypes + EmbeddedWalletConfig: type: object - description: Additional information for business entities + description: | + Per-platform embedded-wallet configuration. Controls branding and OTP + behavior for the email sent when a customer authenticates with an + EMAIL_OTP credential. Fields omitted from a request are left unchanged. properties: - legalName: - type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: + appName: type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: + maxLength: 255 + description: App name displayed in the default OTP email template. + example: Acme Wallet + otpLength: + type: integer + minimum: 4 + maximum: 12 + description: | + Number of digits / characters in the OTP code. Defaults to 6 when + not set. + example: 6 + alphanumeric: + type: boolean + description: | + If true, OTP includes letters in addition to digits. Defaults to + numeric-only when not set. + example: false + expirationSeconds: + type: integer + minimum: 1 + maximum: 86400 + description: | + OTP validity window in seconds. Defaults to 300 when not set. + example: 300 + sendFromEmailAddress: type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: + format: email + maxLength: 255 + description: Custom sender email address for OTP emails. + example: noreply@acme.com + sendFromEmailSenderName: type: string - description: Business registration number - example: '5523041' - incorporatedOn: + maxLength: 255 + description: | + Custom sender display name. Defaults to "Notifications" when not set. + example: Acme Notifications + replyToEmailAddress: type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: + format: email + maxLength: 255 + description: Custom reply-to email address for OTP emails. + example: support@acme.com + logoUrl: type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: - type: array - items: - type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) - example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: + format: uri + maxLength: 512 + description: URL to a PNG logo for the OTP email. Resized to 340x124px. + example: https://acme.com/logo.png + PlatformConfig: + type: object + properties: + id: type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: + description: System-generated unique identifier + readOnly: true + example: PlatformConfig:019542f5-b3e7-1d02-0000-000000000003 + umaDomain: type: string - enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + description: UMA domain for this platform + example: platform.uma.domain + proxyUmaSubdomain: type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + description: The subdomain that incoming requests will be proxied to + example: platform + webhookEndpoint: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: + description: URL where webhook notifications will be sent + example: https://api.mycompany.com/webhooks/uma + supportedCurrencies: type: array items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + $ref: '#/components/schemas/PlatformCurrencyConfig' + description: | + List of currencies supported by the platform. This is what the platform's + customers are able to hold, send, and receive. + isRegulatedFinancialInstitution: + type: boolean + description: | + Whether the platform is a regulated financial institution. This is used to + determine if the platform's customers must be KYC/KYB'd by Lightspark via + the KYC link flow. This can only be set by Lightspark during platform + creation. + example: false + embeddedWalletConfig: + $ref: '#/components/schemas/EmbeddedWalletConfig' + description: | + Embedded-wallet branding and OTP settings for this platform. Present + only when the platform has configured embedded-wallet support; + omitted otherwise. + createdAt: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + format: date-time + description: Creation timestamp + readOnly: true + example: '2025-06-15T12:30:45Z' + updatedAt: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: - type: array - items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BusinessCustomerFields: + format: date-time + description: Last update timestamp + readOnly: true + example: '2025-06-15T12:30:45Z' + Error401: type: object required: - - customerType + - message + - status + - code properties: - customerType: + status: + type: integer + enum: + - 401 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | UNAUTHORIZED | Issue with API credentials | + | INVALID_SIGNATURE | Signature header is invalid | + | WALLET_SIGNATURE_MISSING | The `Grid-Wallet-Signature` header is required for this Embedded Wallet action but was not supplied | + | WALLET_SIGNATURE_MALFORMED | The `Grid-Wallet-Signature` header could not be parsed (bad encoding, structure, or fields) | + | WALLET_SIGNATURE_BODY_MISMATCH | The `Grid-Wallet-Signature` was computed over a different request body than the one received | + | WALLET_SIGNATURE_INVALID | The `Grid-Wallet-Signature` failed cryptographic verification against the registered credential | + | REQUEST_ID_MISSING | The `Request-Id` header is required on the signed retry but was not supplied (paired with `Grid-Wallet-Signature`) | enum: - - BUSINESS - kybStatus: - $ref: '#/components/schemas/KybStatus' - address: - $ref: '#/components/schemas/Address' - businessInfo: - $ref: '#/components/schemas/BusinessInfoUpdate' - BusinessInfoResponse: + - UNAUTHORIZED + - INVALID_SIGNATURE + - WALLET_SIGNATURE_MISSING + - WALLET_SIGNATURE_MALFORMED + - WALLET_SIGNATURE_BODY_MISMATCH + - WALLET_SIGNATURE_INVALID + - REQUEST_ID_MISSING + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error500: type: object - description: | - Business information returned on a customer. `taxId` and `incorporatedOn` are - required on creation but may be absent on legacy customers that pre-date the - requirement, so both are optional in responses. required: - - legalName + - message + - status + - code properties: - legalName: - type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: - type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: - type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: - type: string - description: Business registration number - example: '5523041' - incorporatedOn: - type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: - type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: - type: array - items: - type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) - example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: - type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: - type: string + status: + type: integer enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - 500 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | GRID_SWITCH_ERROR | Grid switch error | + | INTERNAL_ERROR | Internal server or UMA error | enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + - GRID_SWITCH_ERROR + - INTERNAL_ERROR + message: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + PlatformConfigUpdateRequest: + type: object + properties: + umaDomain: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + example: mycompany.com + webhookEndpoint: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: + example: https://api.mycompany.com/webhooks/uma + supportedCurrencies: type: array items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BeneficialOwnerRole: - type: string - enum: - - UBO - - DIRECTOR - - COMPANY_OFFICER - - CONTROL_PERSON - - TRUSTEE - - GENERAL_PARTNER - description: Role of the beneficial owner within the business - example: UBO - BeneficialOwnerPersonalInfo: + $ref: '#/components/schemas/PlatformCurrencyConfig' + embeddedWalletConfig: + $ref: '#/components/schemas/EmbeddedWalletConfig' + description: | + Update or create the embedded-wallet configuration for this platform. + Fields omitted from the nested object are left unchanged. Omit this + field at the top level to leave the embedded-wallet configuration + unchanged entirely. + Error400: type: object required: - - firstName - - lastName - - birthDate - - nationality - - address - - idType - - identifier + - message + - status + - code properties: - firstName: - type: string - description: First name of the individual - example: Jane - middleName: - type: string - description: Middle name of the individual - example: Marie - lastName: + status: + type: integer + enum: + - 400 + description: HTTP status code + code: type: string - description: Last name of the individual - example: Smith - birthDate: + description: | + | Error Code | Description | + |------------|-------------| + | INVALID_INPUT | Invalid input provided | + | MISSING_MANDATORY_USER_INFO | Required customer information is missing | + | INVITATION_ALREADY_CLAIMED | Invitation has already been claimed | + | INVITATIONS_NOT_CONFIGURED | Invitations are not configured | + | INVALID_UMA_ADDRESS | UMA address format is invalid | + | INVITATION_CANCELLED | Invitation has been cancelled | + | QUOTE_REQUEST_FAILED | An issue occurred during the quote process; this is retryable | + | INVALID_PAYREQ_RESPONSE | Counterparty Payreq response was invalid | + | INVALID_RECEIVER | Receiver is invalid | + | PARSE_PAYREQ_RESPONSE_ERROR | Error parsing receiver PayReq response | + | CERT_CHAIN_INVALID | Counterparty certificate chain is invalid | + | CERT_CHAIN_EXPIRED | Counterparty certificate chain has expired | + | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | + | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | + | SENDER_NOT_ACCEPTED | Sender is not accepted | + | AMOUNT_OUT_OF_RANGE | Amount is out of range | + | INVALID_CURRENCY | Currency is invalid | + | INVALID_TIMESTAMP | Timestamp is invalid | + | INVALID_NONCE | Nonce is invalid | + | INVALID_REQUEST_FORMAT | Request format is invalid | + | INVALID_BANK_ACCOUNT | Bank account is invalid | + | SELF_PAYMENT | Self payment not allowed | + | LOOKUP_REQUEST_FAILED | Lookup request failed | + | PARSE_LNURLP_RESPONSE_ERROR | Error parsing LNURLP response | + | INVALID_AMOUNT | Amount is invalid | + | WEBHOOK_ENDPOINT_NOT_SET | Webhook endpoint is not set | + | WEBHOOK_DELIVERY_ERROR | Webhook delivery error | + | LOW_QUALITY | Document quality too low to process | + | DATA_MISMATCH | Document details don't match provided information | + | EXPIRED | Document has expired | + | SUSPECTED_FRAUD | Document suspected of being forged or edited | + | UNSUITABLE_DOCUMENT | Document type is not accepted or not supported | + | INCOMPLETE | Document is missing pages or sides | + | EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS | An EMAIL_OTP credential is already registered on the target internal account; only one email OTP credential is supported per internal account at this time | + | SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time | + | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account | + | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | + | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | + | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | + enum: + - INVALID_INPUT + - MISSING_MANDATORY_USER_INFO + - INVITATION_ALREADY_CLAIMED + - INVITATIONS_NOT_CONFIGURED + - INVALID_UMA_ADDRESS + - INVITATION_CANCELLED + - QUOTE_REQUEST_FAILED + - INVALID_PAYREQ_RESPONSE + - INVALID_RECEIVER + - PARSE_PAYREQ_RESPONSE_ERROR + - CERT_CHAIN_INVALID + - CERT_CHAIN_EXPIRED + - INVALID_PUBKEY_FORMAT + - MISSING_REQUIRED_UMA_PARAMETERS + - SENDER_NOT_ACCEPTED + - AMOUNT_OUT_OF_RANGE + - INVALID_CURRENCY + - INVALID_TIMESTAMP + - INVALID_NONCE + - INVALID_REQUEST_FORMAT + - INVALID_BANK_ACCOUNT + - SELF_PAYMENT + - LOOKUP_REQUEST_FAILED + - PARSE_LNURLP_RESPONSE_ERROR + - INVALID_AMOUNT + - WEBHOOK_ENDPOINT_NOT_SET + - WEBHOOK_DELIVERY_ERROR + - LOW_QUALITY + - DATA_MISMATCH + - EXPIRED + - SUSPECTED_FRAUD + - UNSUITABLE_DOCUMENT + - INCOMPLETE + - EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS + - SMS_OTP_CREDENTIAL_ALREADY_EXISTS + - PASSKEY_CREDENTIAL_ALREADY_EXISTS + - STABLECOIN_PROVIDER_ACCOUNT_INVALID + - STABLECOIN_PROVIDER_ACCOUNT_REVOKED + - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED + message: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1978-06-15' - nationality: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error501: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 501 + description: HTTP status code + code: type: string - description: Country of nationality (ISO 3166-1 alpha-2) - example: US - email: + description: | + | Error Code | Description | + |------------|-------------| + | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory payee data key | + | NOT_IMPLEMENTED | Feature not implemented | + enum: + - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY + - NOT_IMPLEMENTED + message: type: string - format: email - description: Email address of the individual - example: jane.smith@acmecorp.com - phoneNumber: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Currency: + type: object + properties: + code: type: string - description: Phone number in E.164 format - example: '+14155550192' - pattern: ^\+[1-9]\d{1,14}$ - address: - $ref: '#/components/schemas/Address' - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: type: string - description: The identification number or value - example: 123-45-6789 - countryOfIssuance: + description: Full name of the currency + example: United States Dollar + symbol: type: string - description: Country that issued the identification (ISO 3166-1 alpha-2) - example: US - BeneficialOwner: + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + PaymentRail: + type: string + enum: + - ACH + - ACH_COLOMBIA + - BANK_TRANSFER + - BRE_B + - CIPS + - FAST + - FASTER_PAYMENTS + - FEDNOW + - INSTAPAY + - MOBILE_MONEY + - NEFT + - PAYNOW + - PESONET + - PIX + - RTGS + - RTP + - SEPA + - SEPA_INSTANT + - SPEI + - SWIFT + - UNIONPAY + - UPI + - WIRE + description: The payment rail used for the transfer. Payment rails represent the underlying payment network or system used to move funds between accounts. + example: ACH + ExchangeRateFees: + type: object + description: Fees associated with an exchange rate + properties: + fixed: + type: integer + format: int64 + description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) + minimum: 0 + example: 100 + total: + type: integer + format: int64 + description: Total fees in the smallest unit of the sending currency (e.g., cents for USD). This value may change depending on the sending amount used; if no sending amount is specified, it falls back to the default. + minimum: 0 + example: 100 + ExchangeRate: type: object + description: Exchange rate information for a currency corridor required: - - id - - customerId - - roles - - ownershipPercentage - - personalInfo - - kycStatus - - createdAt + - sourceCurrency + - destinationCurrency + - destinationPaymentRail + - minSendingAmount + - maxSendingAmount + - sendingAmount + - receivingAmount + - exchangeRate + - fees + - updatedAt properties: - id: - type: string - description: Unique identifier for this beneficial owner - example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 - customerId: - type: string - description: The ID of the business customer this beneficial owner is associated with - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - roles: - type: array - items: - $ref: '#/components/schemas/BeneficialOwnerRole' - description: Roles of this person within the business - example: - - UBO - - DIRECTOR - ownershipPercentage: + sourceCurrency: + $ref: '#/components/schemas/Currency' + sendingAmount: type: integer - description: Percentage of ownership in the business (0-100) + format: int64 + description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. minimum: 0 - maximum: 100 - example: 51 - personalInfo: - $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' - kycStatus: - $ref: '#/components/schemas/KycStatus' - createdAt: - type: string - format: date-time - description: When this beneficial owner was created - example: '2025-10-03T12:00:00Z' + example: 10000 + minSendingAmount: + type: integer + format: int64 + description: The minimum supported sending amount in the smallest unit of the source currency. + minimum: 0 + example: 100 + maxSendingAmount: + type: integer + format: int64 + description: The maximum supported sending amount in the smallest unit of the source currency. + minimum: 0 + example: 10000000 + destinationCurrency: + $ref: '#/components/schemas/Currency' + destinationPaymentRail: + allOf: + - $ref: '#/components/schemas/PaymentRail' + - description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) + example: UPI + receivingAmount: + type: integer + format: int64 + description: The receiving amount in the smallest unit of the destination currency + minimum: 0 + example: 1650000 + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 0.012121 + fees: + $ref: '#/components/schemas/ExchangeRateFees' updatedAt: type: string format: date-time - description: When this beneficial owner was last updated - example: '2025-10-03T12:00:00Z' - BusinessCustomer: - title: Business Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfoResponse' - beneficialOwners: - type: array - items: - $ref: '#/components/schemas/BeneficialOwner' - CustomerOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomer' - - $ref: '#/components/schemas/BusinessCustomer' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomer' - BUSINESS: '#/components/schemas/BusinessCustomer' - CustomerListResponse: + description: Timestamp when this exchange rate was last refreshed + example: '2025-02-05T12:00:00Z' + ExchangeRateListResponse: type: object required: - data - - hasMore properties: data: type: array - description: List of customers matching the filter criteria + description: List of exchange rates matching the filter criteria items: - $ref: '#/components/schemas/CustomerOneOf' - hasMore: - type: boolean - description: Indicates if more results are available beyond this page - nextCursor: - type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) - totalCount: - type: integer - description: Total number of customers matching the criteria (excluding pagination) - Error405: + $ref: '#/components/schemas/ExchangeRate' + DiscoveryListResponse: + type: object + properties: + data: + type: array + description: List of payment rails matching the filter criteria + items: + type: object + required: + - bankName + - displayName + - country + - currency + properties: + bankName: + type: string + description: | + Canonical name for this payment rail. Pass this value as `bankName` when creating an external account. + displayName: + type: string + description: Human-friendly display name for this payment rail. + country: + type: string + description: ISO 3166-1 alpha-2 country code. + currency: + type: string + description: ISO 4217 currency code. + CustomerType: + type: string + enum: + - INDIVIDUAL + - BUSINESS + description: Whether the customer is an individual or a business entity + example: INDIVIDUAL + ContactVerificationStatus: + type: string + description: Status of an individual contact-verification channel (email or phone). `PENDING` means verification is required but not yet completed; `VERIFIED` means the channel has been confirmed. + enum: + - PENDING + - VERIFIED + example: VERIFIED + ContactVerification: type: object - required: - - message - - status - - code + description: |- + Email and/or phone verification state for the customer. This object is **only present when the customer's regulatory jurisdiction requires contact verification** (e.g. EU customers). For customers who have no such requirement, this object is omitted entirely — no action is needed. + + Each channel is reported independently: only the channels the customer's provider actually requires are present. A provider may require both email and phone, just one of them, or — when the object is absent — neither. Every channel that **is** present must reach `VERIFIED` before the customer can begin KYC. Drive each present channel with `POST /customers/{customerId}/verify-email` and/or `POST /customers/{customerId}/verify-phone` (and their `/confirm` sub-routes). properties: - status: - type: integer - enum: - - 405 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | - enum: - - METHOD_NOT_ALLOWED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerCreateRequest: + email: + $ref: '#/components/schemas/ContactVerificationStatus' + description: Verification status of the customer's email address. Present only when the provider requires email verification. + phone: + $ref: '#/components/schemas/ContactVerificationStatus' + description: Verification status of the customer's phone number. Present only when the provider requires phone verification. + Customer: type: object required: + - umaAddress + - platformCustomerId - customerType properties: + id: + type: string + description: System-generated unique identifier + readOnly: true + example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific customer identifier. If not provided, one will be generated by the system. + description: Platform-specific customer identifier example: 9f84e0c2a72c4fa customerType: $ref: '#/components/schemas/CustomerType' region: type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity and regulatory jurisdiction. example: US currencies: type: array items: type: string - description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. + description: List of currency codes enabled for this customer. example: - USD - USDC email: type: string format: email - description: Email address for the customer. **Required in regions that verify the email address before identity verification** (e.g. the EU); optional otherwise. + description: Email address for the customer. example: john.doe@example.com phoneNumber: type: string pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. **Required in regions that verify the phone number before identity verification** (e.g. the EU); optional otherwise. + description: Phone number for the customer in strict E.164 format. example: '+14155551234' umaAddress: type: string - description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + description: Full UMA address (always present in responses, even if system-generated). This is an optional identifier to route payments to the customer. example: $john.doe@uma.domain.com - IndividualCustomerCreateRequest: - title: Individual Customer Create Request + createdAt: + type: string + format: date-time + description: Creation timestamp + readOnly: true + example: '2025-07-21T17:32:28Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp + readOnly: true + example: '2025-07-21T17:32:28Z' + isDeleted: + type: boolean + description: Whether the customer is marked as deleted + example: false + readOnly: true + contactVerification: + allOf: + - $ref: '#/components/schemas/ContactVerification' + readOnly: true + description: Email and phone verification state. **Only present when the customer's payment provider requires it** (e.g. EU customers); omitted otherwise. + KycStatus: + type: string + enum: + - UNVERIFIED + - PENDING + - APPROVED + - REJECTED + - HOLD + description: The current KYC status of a customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. + example: APPROVED + Address: + type: object + required: + - line1 + - postalCode + - country + properties: + line1: + type: string + description: Street address line 1 + example: 123 Main Street + line2: + type: string + description: Street address line 2 + example: Apt 4B + city: + type: string + description: City + example: San Francisco + state: + type: string + description: State/Province/Region + example: CA + postalCode: + type: string + description: Postal/ZIP code + example: '94105' + country: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + IndividualCustomerFields: + type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - INDIVIDUAL + kycStatus: + $ref: '#/components/schemas/KycStatus' + fullName: + type: string + description: Individual's full name + example: John Michael Doe + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1990-01-15' + nationality: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + address: + $ref: '#/components/schemas/Address' + IndividualCustomer: + title: Individual Customer allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/Customer' - $ref: '#/components/schemas/IndividualCustomerFields' - - type: object - properties: - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - writeOnly: true - description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. - example: 123-45-6789 - BusinessInfo: + KybStatus: + type: string + enum: + - UNVERIFIED + - PENDING + - APPROVED + - REJECTED + - HOLD + description: The current KYB status of a business customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information. + example: APPROVED + EntityType: + type: string + enum: + - SOLE_PROPRIETORSHIP + - PARTNERSHIP + - LLC + - CORPORATION + - S_CORPORATION + - NON_PROFIT + - OTHER + description: Legal entity type of the business + example: LLC + BusinessType: + type: string + description: The high-level industry category of the business + enum: + - AGRICULTURE_FORESTRY_FISHING_AND_HUNTING + - MINING_QUARRYING_AND_OIL_AND_GAS_EXTRACTION + - UTILITIES + - CONSTRUCTION + - MANUFACTURING + - WHOLESALE_TRADE + - RETAIL_TRADE + - TRANSPORTATION_AND_WAREHOUSING + - INFORMATION + - FINANCE_AND_INSURANCE + - REAL_ESTATE_AND_RENTAL_AND_LEASING + - PROFESSIONAL_SCIENTIFIC_AND_TECHNICAL_SERVICES + - MANAGEMENT_OF_COMPANIES_AND_ENTERPRISES + - ADMINISTRATIVE_AND_SUPPORT_AND_WASTE_MANAGEMENT_AND_REMEDIATION_SERVICES + - EDUCATIONAL_SERVICES + - HEALTH_CARE_AND_SOCIAL_ASSISTANCE + - ARTS_ENTERTAINMENT_AND_RECREATION + - ACCOMMODATION_AND_FOOD_SERVICES + - OTHER_SERVICES + - PUBLIC_ADMINISTRATION + PurposeOfAccount: + type: string + enum: + - CONTRACTOR_PAYOUTS + - CREATOR_PAYOUTS + - EMPLOYEE_PAYOUTS + - MARKETPLACE_SELLER_PAYOUTS + - SUPPLIER_PAYMENTS + - CROSS_BORDER_B2B + - AR_AUTOMATION + - AP_AUTOMATION + - EMBEDDED_PAYMENTS + - PLATFORM_FEE_COLLECTION + - P2P_TRANSFERS + - CHARITABLE_DONATIONS + - OTHER + description: The intended purpose for using the Grid account + example: CONTRACTOR_PAYOUTS + SourceOfFundsCategory: + type: string + enum: + - OPERATING_REVENUE + - INVESTMENT_INCOME + - LOANS + - VENTURE_CAPITAL + - PERSONAL_SAVINGS + - DONATIONS + - OTHER + description: A structured source-of-funds category for the business + example: OPERATING_REVENUE + BusinessInfoUpdate: type: object - description: Additional information required for business entities - required: - - legalName - - taxId - - incorporatedOn + description: Additional information for business entities properties: legalName: type: string @@ -12570,641 +10216,533 @@ components: description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) example: - US - BusinessCustomerCreateRequest: - title: Business Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - required: - - businessInfo - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfo' - CustomerCreateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerCreateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' - Error409: + BusinessCustomerFields: type: object required: - - message - - status - - code + - customerType properties: - status: - type: integer - enum: - - 409 - description: HTTP status code - code: + customerType: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | - | TRANSACTION_NOT_CANCELLABLE | Transaction has already settled or is otherwise past the point where it can be cancelled | - | UMA_ADDRESS_EXISTS | UMA address already exists | - | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | - | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | - | PASSKEY_ALREADY_ENROLLED | The customer already has an enrolled passkey factor; only one passkey per customer is supported. Delete the existing one before enrolling another | - | SCA_SESSION_REQUIRED | The customer's Strong Customer Authentication login session is missing or expired. Re-authenticate the customer, then retry the request. Distinct from a `401`, which means the platform's own API credentials were rejected | - | BENEFICIARY_TRUSTED | The external account is currently a trusted beneficiary, so it cannot be deleted. Untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete | - | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | enum: - - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL - - TRANSACTION_NOT_CANCELLABLE - - UMA_ADDRESS_EXISTS - - EMAIL_OTP_EMAIL_ALREADY_EXISTS - - EMAIL_OTP_CREDENTIAL_SET_CHANGED - - PASSKEY_ALREADY_ENROLLED - - SCA_SESSION_REQUIRED - - BENEFICIARY_TRUSTED - - CONFLICT - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error404: + - BUSINESS + kybStatus: + $ref: '#/components/schemas/KybStatus' + address: + $ref: '#/components/schemas/Address' + businessInfo: + $ref: '#/components/schemas/BusinessInfoUpdate' + BusinessInfoResponse: type: object + description: | + Business information returned on a customer. `taxId` and `incorporatedOn` are + required on creation but may be absent on legacy customers that pre-date the + requirement, so both are optional in responses. required: - - message - - status - - code + - legalName properties: - status: - type: integer - enum: - - 404 - description: HTTP status code - code: + legalName: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_FOUND | Transaction not found | - | INVITATION_NOT_FOUND | Invitation not found | - | USER_NOT_FOUND | Customer not found | - | QUOTE_NOT_FOUND | Quote not found | - | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | - | TOKEN_NOT_FOUND | Token not found | - | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | - | REFERENCE_NOT_FOUND | Reference not found | - | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | - | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | - enum: - - TRANSACTION_NOT_FOUND - - INVITATION_NOT_FOUND - - USER_NOT_FOUND - - QUOTE_NOT_FOUND - - LOOKUP_REQUEST_NOT_FOUND - - TOKEN_NOT_FOUND - - BULK_UPLOAD_JOB_NOT_FOUND - - REFERENCE_NOT_FOUND - - UMA_NOT_FOUND - - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND - message: + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error410: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 410 - description: HTTP status code - code: + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: type: string - description: | - | Error Code | Description | - |------------|-------------| - | CUSTOMER_DELETED | Customer has been permanently deleted | - enum: - - CUSTOMER_DELETED - message: + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerUpdateRequest: - title: Customer Update Request - description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. - type: object - required: - - customerType - properties: - customerType: - $ref: '#/components/schemas/CustomerType' - currencies: + description: Business registration number + example: '5523041' + incorporatedOn: + type: string + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: + type: string + description: Tax identification number + example: 47-1234567 + countriesOfOperation: type: array items: type: string - description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - USD - - EUR - - USDC - email: - type: string - format: email - description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. - example: '+14155551234' - umaAddress: - type: string - description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerUpdateRequest: - title: Individual Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - BusinessCustomerUpdateRequest: - title: Business Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - CustomerUpdateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - SignedRequestChallenge: - title: Signed Request Challenge - type: object - required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. - properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' - requestId: - type: string - description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: - type: string - format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' - Error424: - type: object - required: - - message - - status - - code - properties: - status: - type: integer + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: + type: string enum: - - 424 - description: HTTP status code - code: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: type: string - description: | - | Error Code | Description | - |------------|-------------| - | PAYREQ_REQUEST_FAILED | Payment request failed | - | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | - | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | - | LNURLP_REQUEST_FAILED | LNURLP request failed | - | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | enum: - - PAYREQ_REQUEST_FAILED - - COUNTERPARTY_PUBKEY_FETCH_ERROR - - NO_COMPATIBLE_UMA_VERSION - - LNURLP_REQUEST_FAILED - - EMAIL_OTP_CREDENTIAL_SYNC_FAILED - message: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - KycLinkCreateRequest: - type: object - description: Request body for generating a hosted KYC link for an existing customer. - properties: - redirectUri: + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: type: string - format: uri - description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. - example: https://app.example.com/onboarding/completed - KycProvider: + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BeneficialOwnerRole: type: string - description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. enum: - - SUMSUB - example: SUMSUB - KycLinkResponse: + - UBO + - DIRECTOR + - COMPANY_OFFICER + - CONTROL_PERSON + - TRUSTEE + - GENERAL_PARTNER + description: Role of the beneficial owner within the business + example: UBO + IdentificationType: + type: string + enum: + - SSN + - ITIN + - EIN + - NON_US_TAX_ID + description: Type of tax identification + example: SSN + BeneficialOwnerPersonalInfo: type: object - description: A hosted KYC link that the customer can complete to verify their identity. required: - - kycUrl - - expiresAt - - provider + - firstName + - lastName + - birthDate + - nationality + - address + - idType + - identifier properties: - kycUrl: + firstName: type: string - description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. - example: https://kyc.lightspark.com/onboard/abc123def456 - expiresAt: + description: First name of the individual + example: Jane + middleName: type: string - format: date-time - description: Time at which the hosted link expires and can no longer be used. - example: '2027-01-15T14:32:00Z' - provider: - $ref: '#/components/schemas/KycProvider' - token: + description: Middle name of the individual + example: Marie + lastName: type: string - description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. - example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ContactVerificationConfirmRequest: - type: object - description: Request body for confirming an email or phone verification challenge. - required: - - code - properties: - code: + description: Last name of the individual + example: Smith + birthDate: type: string - description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. - example: '123456' - ScaFactor: - type: string - enum: - - SMS_OTP - - TOTP - - PASSKEY - description: | - A Strong Customer Authentication factor. - - | Factor | Description | - |--------|-------------| - | `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. | - | `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). | - | `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. | - ScaFactorView: + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1978-06-15' + nationality: + type: string + description: Country of nationality (ISO 3166-1 alpha-2) + example: US + email: + type: string + format: email + description: Email address of the individual + example: jane.smith@acmecorp.com + phoneNumber: + type: string + description: Phone number in E.164 format + example: '+14155550192' + pattern: ^\+[1-9]\d{1,14}$ + address: + $ref: '#/components/schemas/Address' + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + description: The identification number or value + example: 123-45-6789 + countryOfIssuance: + type: string + description: Country that issued the identification (ISO 3166-1 alpha-2) + example: US + BeneficialOwner: type: object - description: An enrolled Strong Customer Authentication factor. required: - - factor + - id + - customerId + - roles + - ownershipPercentage + - personalInfo + - kycStatus + - createdAt properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The kind of enrolled factor. - credentialId: + id: type: string - description: The per-credential id, populated only for `PASSKEY` factors (the id passed to delete a passkey). Omitted for `TOTP` and `SMS_OTP`, which have no per-credential id. - name: + description: Unique identifier for this beneficial owner + example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 + customerId: + type: string + description: The ID of the business customer this beneficial owner is associated with + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + roles: + type: array + items: + $ref: '#/components/schemas/BeneficialOwnerRole' + description: Roles of this person within the business + example: + - UBO + - DIRECTOR + ownershipPercentage: + type: integer + description: Percentage of ownership in the business (0-100) + minimum: 0 + maximum: 100 + example: 51 + personalInfo: + $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' + kycStatus: + $ref: '#/components/schemas/KycStatus' + createdAt: + type: string + format: date-time + description: When this beneficial owner was created + example: '2025-10-03T12:00:00Z' + updatedAt: type: string - description: An optional human-readable label for this factor. - ScaFactorList: + format: date-time + description: When this beneficial owner was last updated + example: '2025-10-03T12:00:00Z' + BusinessCustomer: + title: Business Customer + allOf: + - $ref: '#/components/schemas/Customer' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfoResponse' + beneficialOwners: + type: array + items: + $ref: '#/components/schemas/BeneficialOwner' + CustomerOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomer' + - $ref: '#/components/schemas/BusinessCustomer' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomer' + BUSINESS: '#/components/schemas/BusinessCustomer' + CustomerListResponse: type: object - description: The Strong Customer Authentication factors a customer has enrolled. required: - - factors + - data + - hasMore properties: - factors: + data: type: array - description: The customer's enrolled SCA factors. + description: List of customers matching the filter criteria items: - $ref: '#/components/schemas/ScaFactorView' - TotpFactorEnrollRequest: - type: object - title: TOTP Factor Enroll Request - description: Start enrolling a time-based one-time-password (TOTP) authenticator factor. - required: - - type - properties: - type: + $ref: '#/components/schemas/CustomerOneOf' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: type: string - enum: - - TOTP - description: Discriminator selecting the TOTP factor. TOTP enrollment needs no other input at start. - PasskeyFactorEnrollRequest: + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of customers matching the criteria (excluding pagination) + Error405: type: object - title: Passkey Factor Enroll Request - description: Start enrolling a WebAuthn passkey factor. required: - - type + - message + - status + - code properties: - type: - type: string + status: + type: integer enum: - - PASSKEY - description: Discriminator selecting the passkey factor. Passkey enrollment needs no other input at start. - ScaFactorEnrollRequestOneOf: - oneOf: - - $ref: '#/components/schemas/TotpFactorEnrollRequest' - - $ref: '#/components/schemas/PasskeyFactorEnrollRequest' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpFactorEnrollRequest' - PASSKEY: '#/components/schemas/PasskeyFactorEnrollRequest' - description: Which SCA factor to begin enrolling, selected by `type`. `SMS_OTP` is not enrollable (it uses the customer's verified phone), so only `TOTP` and `PASSKEY` are valid here. - TotpEnrollmentStart: - type: object - description: The shared secret a customer's authenticator app needs to enroll a TOTP factor. Returned by `POST /sca/factors` for a `TOTP` request; the customer scans `totpUri` (an `otpauth://` provisioning URI) and confirms with the first code their app produces. - required: - - type - - secret - - secretBase32Encoded - - totpUri - properties: - type: + - 405 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | enum: - - TOTP - description: Discriminator identifying this as the TOTP enrollment-start payload. - secret: - type: string - description: The raw TOTP shared secret. - secretBase32Encoded: - type: string - description: The Base32-encoded shared secret, suitable for manual entry into an authenticator app that does not scan QR codes. - totpUri: + - METHOD_NOT_ALLOWED + message: type: string - description: The `otpauth://` provisioning URI (the QR-code payload) the customer's authenticator app scans to enroll the factor. - example: otpauth://totp/Grid:customer@example.com?secret=ABC123&issuer=Grid - PasskeyEnrollmentStart: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerCreateRequest: type: object - description: Opaque WebAuthn registration options relayed to the end user's device to enroll a passkey factor. Grid performs no crypto; pass `options` to the device's WebAuthn API to produce a credential, then submit that credential to the confirm endpoint unmodified. required: - - type - - options - - allowedOrigins - - relyingPartyId + - customerType properties: - type: + platformCustomerId: type: string - enum: - - PASSKEY - description: Discriminator identifying this as the passkey enrollment-start payload. - options: - type: object - additionalProperties: true - description: Opaque WebAuthn `PublicKeyCredentialCreationOptions`. Pass to the device's WebAuthn registration API unmodified. - allowedOrigins: + description: Platform-specific customer identifier. If not provided, one will be generated by the system. + example: 9f84e0c2a72c4fa + customerType: + $ref: '#/components/schemas/CustomerType' + region: + type: string + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + example: US + currencies: type: array - description: The origins the WebAuthn registration ceremony may run against. The origin the credential is produced against must be one of these and must be echoed back on the confirm call. items: type: string + description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. example: - - https://app.example.com - relyingPartyId: + - USD + - USDC + email: type: string - description: The WebAuthn relying-party id the credential is bound to. - example: app.example.com - ScaFactorEnrollStartOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentStart' - - $ref: '#/components/schemas/PasskeyEnrollmentStart' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentStart' - PASSKEY: '#/components/schemas/PasskeyEnrollmentStart' - description: 'The factor-specific material needed to complete enrollment, keyed by `type`: a TOTP shared secret + provisioning URI, or the WebAuthn registration options for a passkey.' - TotpEnrollmentConfirmRequest: + format: email + description: Email address for the customer. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerCreateRequest: + title: Individual Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + - type: object + properties: + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + writeOnly: true + description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. + example: 123-45-6789 + BusinessInfo: type: object - description: The shared secret returned by the TOTP enrollment start, plus the first code the customer's authenticator app produces, submitted to confirm and finalize the TOTP factor. + description: Additional information required for business entities required: - - type - - secret - - code + - legalName + - taxId + - incorporatedOn properties: - type: + legalName: type: string - enum: - - TOTP - description: Discriminator selecting the TOTP confirm variant. - secret: + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: type: string - description: The shared secret returned as `secret` by the TOTP enrollment start, threaded back to bind the confirmation to that enrollment. - code: + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: type: string - description: The current time-based one-time code from the customer's authenticator app. In sandbox, the code is always `123456`. - example: '123456' - PasskeyEnrollmentConfirmRequest: - type: object - description: The WebAuthn credential a device produced for a passkey registration challenge, submitted to enroll the passkey factor. - required: - - type - - origin - - credential - properties: - type: + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: type: string - enum: - - PASSKEY - description: Discriminator selecting the passkey confirm variant. - origin: + description: Business registration number + example: '5523041' + incorporatedOn: type: string - description: The WebAuthn origin the `credential` was produced against (one of the enrollment start's `allowedOrigins`). - example: https://app.example.com - credential: - type: object - additionalProperties: true - description: Opaque WebAuthn credential the device produced from the enrollment start's `options`. - ScaFactorConfirmRequestOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentConfirmRequest' - - $ref: '#/components/schemas/PasskeyEnrollmentConfirmRequest' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentConfirmRequest' - PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmRequest' - description: 'The proof that finalizes enrollment, keyed by `type`: the TOTP shared secret + code, or the passkey `origin` + `credential`.' - TotpEnrollmentConfirmResponse: - type: object - description: The one-time recovery codes issued once a TOTP factor is enrolled. These are shown to the customer only once; store them somewhere safe to recover access if the authenticator device is lost. - required: - - type - - recoveryCodes - properties: - type: + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: type: string - enum: - - TOTP - description: Discriminator identifying this as the TOTP enrollment result. - recoveryCodes: + description: Tax identification number + example: 47-1234567 + countriesOfOperation: type: array - description: The one-time recovery codes for this TOTP factor. items: type: string + description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - ABCD-EFGH-IJKL - - MNOP-QRST-UVWX - PasskeyEnrollmentConfirmResponse: - type: object - description: The enrolled passkey factor returned after a successful confirmation. - required: - - type - - factor - properties: - type: + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: type: string enum: - - PASSKEY - description: Discriminator identifying this as the passkey enrollment result. - factor: - $ref: '#/components/schemas/ScaFactorView' - ScaFactorConfirmResponseOneOf: - oneOf: - - $ref: '#/components/schemas/TotpEnrollmentConfirmResponse' - - $ref: '#/components/schemas/PasskeyEnrollmentConfirmResponse' - discriminator: - propertyName: type - mapping: - TOTP: '#/components/schemas/TotpEnrollmentConfirmResponse' - PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmResponse' - description: The enrollment result, keyed by `type`. - ScaLoginStartRequest: - type: object - description: Selects which enrolled factor to start an SCA login with. The factor must already be enrolled (or, for `SMS_OTP`, the phone verified). - required: - - factor - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor to authenticate with. - ScaLoginStart: - type: object - description: 'The factor-specific material a customer needs to complete an SCA login. Each factor surfaces only the fields it issues: `SMS_OTP` carries `challengeId` and `expiresAt`; `TOTP` carries neither (the customer reads the code from their authenticator app); `PASSKEY` carries the opaque WebAuthn `passkeyOptions` with `allowedOrigins` and `relyingPartyId`.' - required: - - factor - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor this login was started for. - challengeId: - type: - - string - - 'null' - description: The challenge handle for an `SMS_OTP` login, threaded back on the complete call. Present only for `SMS_OTP`. - expiresAt: - type: - - string - - 'null' - format: date-time - description: Absolute UTC timestamp after which the `SMS_OTP` code expires. Present only for `SMS_OTP`. - example: '2025-10-03T12:05:00Z' - passkeyOptions: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion request options. Present only for `PASSKEY`; pass to the device's WebAuthn API to produce the assertion submitted on the complete call. - allowedOrigins: - type: - - array - - 'null' + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array items: type: string - description: The origins the WebAuthn ceremony may run against. Present only for `PASSKEY`. + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) example: - - https://app.example.com - relyingPartyId: - type: - - string - - 'null' - description: The WebAuthn relying-party id. Present only for `PASSKEY`. - example: app.example.com - ScaLoginCompleteRequest: - type: object - description: Completes an SCA login by submitting the proof for the started factor. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`), plus the `factor` being completed, the `endUserIpAddress` the login is being performed from, and, for `SMS_OTP`, the `challengeId` returned by the login start. - required: - - factor - - endUserIpAddress - anyOf: - - required: - - code - - required: - - passkeyAssertion - - origin - properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor being completed; must match the started login. - endUserIpAddress: - type: string - description: The IP address of the end user's device completing this login, recorded against the login event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and any transaction-risk exemption. - example: 203.0.113.42 - challengeId: - type: - - string - - 'null' - description: The challenge handle returned by the login start, required for `SMS_OTP` and omitted for other factors. - code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. - example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the login start's `passkeyOptions`. Required when completing a `PASSKEY` login. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against (one of the login start's `allowedOrigins`). Required alongside `passkeyAssertion`; omit it for the `code` path. - example: https://app.example.com - ScaLoginComplete: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BusinessCustomerCreateRequest: + title: Business Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfo' + CustomerCreateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' + Error409: type: object - description: The status of a completed SCA login session. required: + - message - status + - code properties: status: + type: integer + enum: + - 409 + description: HTTP status code + code: type: string - description: The status of the login session. A successful login reports `SUCCESS`; other values indicate the login did not complete and should be surfaced to the caller. - example: SUCCESS - sessionExpiresAt: - type: - - string - - 'null' - format: date-time - description: Absolute UTC timestamp after which the customer's SCA session is no longer valid and they must complete another SCA login. Money movement in SCA-regulated currencies is refused once it passes, so prompt a re-login ahead of it rather than waiting for a `SCA_SESSION_REQUIRED` failure. Present when the login established a session. - example: '2026-01-29T12:00:00Z' - Error423: + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | + | UMA_ADDRESS_EXISTS | UMA address already exists | + | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | + | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | + | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | + enum: + - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL + - UMA_ADDRESS_EXISTS + - EMAIL_OTP_EMAIL_ALREADY_EXISTS + - EMAIL_OTP_CREDENTIAL_SET_CHANGED + - CONFLICT + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error404: type: object required: - message @@ -13214,117 +10752,142 @@ components: status: type: integer enum: - - 423 + - 404 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | ACCOUNT_LOCKED | The customer's login is temporarily locked (or suspended) after too many failed attempts. Inspect `details.lockedUntil` for when the customer may retry, and `details.failedAttempts` for the current count. | + | TRANSACTION_NOT_FOUND | Transaction not found | + | INVITATION_NOT_FOUND | Invitation not found | + | USER_NOT_FOUND | Customer not found | + | QUOTE_NOT_FOUND | Quote not found | + | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | + | TOKEN_NOT_FOUND | Token not found | + | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | + | REFERENCE_NOT_FOUND | Reference not found | + | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | + | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | enum: - - ACCOUNT_LOCKED + - TRANSACTION_NOT_FOUND + - INVITATION_NOT_FOUND + - USER_NOT_FOUND + - QUOTE_NOT_FOUND + - LOOKUP_REQUEST_NOT_FOUND + - TOKEN_NOT_FOUND + - BULK_UPLOAD_JOB_NOT_FOUND + - REFERENCE_NOT_FOUND + - UMA_NOT_FOUND + - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND message: type: string description: Error message details: type: object - description: Lockout detail. `lockedUntil` is the UTC timestamp the customer may retry after (absent when suspended indefinitely); `failedAttempts` is the current cumulative failed-login count; `suspended` is true when the account is locked with no automatic expiry. - properties: - suspended: - type: boolean - lockedUntil: - type: - - string - - 'null' - format: date-time - failedAttempts: - type: integer + description: Additional error details additionalProperties: true - RecordSecurityEventRequest: + Error410: type: object - description: Records a client-side security event for the customer so Grid can maintain the customer's login-security state (SCA session revocation and failed-login lockout). required: - - eventType + - message + - status + - code properties: - eventType: - type: string + status: + type: integer enum: - - RESET_PASSWORD_COMPLETED - - FAILED_LOGIN_ATTEMPT - description: | - The security event to record: - - | Value | Effect | - |-------|--------| - | `RESET_PASSWORD_COMPLETED` | Revokes every active SCA session for the customer and clears the failed-login counter. | - | `FAILED_LOGIN_ATTEMPT` | Increments the cumulative failed-login counter and escalates the lockout at each milestone: 5 attempts → 15 minutes, 6 → 30 minutes, 7 → 1 hour, 8 → 24 hours, 9 or more → suspension. | - example: FAILED_LOGIN_ATTEMPT - RecordSecurityEventResponse: - type: object - description: The customer's login-security state after recording the event, so the integrator can surface a lockout to the end user. - required: - - eventType - - suspended - - failedAttempts - properties: - eventType: + - 410 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | enum: - - RESET_PASSWORD_COMPLETED - - FAILED_LOGIN_ATTEMPT - description: The event that was recorded. - example: FAILED_LOGIN_ATTEMPT - suspended: - type: boolean - description: Whether the customer's login is currently suspended (locked with no automatic expiry). A suspended customer must go through a password reset (`RESET_PASSWORD_COMPLETED`) to clear the lockout. - lockedUntil: - type: - - string - - 'null' - format: date-time - description: When the customer may attempt to log in again, if temporarily locked. Null when not locked, or when `suspended` is true (no automatic expiry). - example: '2025-10-03T12:15:00Z' - failedAttempts: - type: integer - description: The customer's current cumulative failed-login count. - example: 5 - TwoFactorResetStartRequest: + - CUSTOMER_DELETED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerUpdateRequest: + title: Customer Update Request + description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. type: object - description: Selects which enrolled factor to reset via the liveness-gated recovery flow. required: - - factor + - customerType properties: - factor: - $ref: '#/components/schemas/ScaFactor' - description: The enrolled factor to reset. - TwoFactorResetStart: + customerType: + $ref: '#/components/schemas/CustomerType' + currencies: + type: array + items: + type: string + description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + example: + - USD + - EUR + - USDC + email: + type: string + format: email + description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerUpdateRequest: + title: Individual Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + BusinessCustomerUpdateRequest: + title: Business Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + CustomerUpdateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + SignedRequestChallenge: + title: Signed Request Challenge type: object - description: The reset handle plus the opaque liveness handles a caller relays to the end-user device to complete the liveness check. `resetId` threads the ceremony together (status and complete reference it). `livenessAccessToken` and `verificationLink` are omitted when they are not returned. required: - - resetId + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. properties: - resetId: + payloadToSign: type: string - description: Identifier for this reset; pass it to the status and complete endpoints. - livenessAccessToken: - type: - - string - - 'null' - description: Access token for the embedded liveness/verification SDK, bound to this reset. Omitted when one is not returned. - verificationLink: - type: - - string - - 'null' - description: Hosted identity-verification page URL for completing liveness. Omitted when one is not returned. + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' + requestId: + type: string + description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 expiresAt: - type: - - string - - 'null' + type: string format: date-time - description: Absolute UTC timestamp at the end of the reset window. Omitted when one is not returned. - example: '2025-10-03T12:30:00Z' - Error429: + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' + Error424: type: object required: - message @@ -13334,16 +10897,24 @@ components: status: type: integer enum: - - 429 + - 424 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | + | PAYREQ_REQUEST_FAILED | Payment request failed | + | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | + | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | + | LNURLP_REQUEST_FAILED | LNURLP request failed | + | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | enum: - - RATE_LIMITED + - PAYREQ_REQUEST_FAILED + - COUNTERPARTY_PUBKEY_FETCH_ERROR + - NO_COMPATIBLE_UMA_VERSION + - LNURLP_REQUEST_FAILED + - EMAIL_OTP_CREDENTIAL_SYNC_FAILED message: type: string description: Error message @@ -13351,175 +10922,54 @@ components: type: object description: Additional error details additionalProperties: true - TwoFactorResetStatus: + KycLinkCreateRequest: type: object - description: The status of an in-progress 2FA reset, polled until it reaches a terminal value. - required: - - status - - factor - - expiresAt + description: Request body for generating a hosted KYC link for an existing customer. properties: - status: - type: string - enum: - - INITIATED - - PENDING_REVIEW - - LIVENESS_PASSED - - COMPLETED - - REJECTED - - EXPIRED - description: |- - The reset status. - | Value | Terminal | Meaning | |-------|----------|---------| | `INITIATED` | no | Reset started; liveness not yet submitted. Keep polling. | | `PENDING_REVIEW` | no | Liveness submitted; under review. Keep polling. | | `LIVENESS_PASSED` | no | Liveness passed; call the complete endpoint to finish the reset. | | `COMPLETED` | **yes** | Reset finished; the lost factor is cleared and re-enrollment can begin. | | `REJECTED` | **yes** | Liveness failed. Stop polling; start a new reset. | | `EXPIRED` | **yes** | The reset window closed before it completed. Stop polling; start a new reset. | - Stop polling on any terminal value. - example: INITIATED - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor being reset. - enrollmentStatus: - type: - - string - - 'null' - enum: - - PENDING - - COMPLETED - - null - description: The enrollment status of the replacement factor. `PENDING` until the customer finishes re-enrolling, then `COMPLETED`. Null for an `SMS_OTP` reset, where completing the reset enrolls the new number directly with no separate enrollment step. - example: PENDING - expiresAt: + redirectUri: type: string - format: date-time - description: When the reset window closes. Poll no longer than this. - example: '2025-10-03T12:15:00Z' - completedAt: - type: - - string - - 'null' - format: date-time - description: When the reset completed; null until then. - example: null - TwoFactorResetCompleteRequest: - type: object - description: Optional body for completing a 2FA reset. Only needed when resetting the `SMS_OTP` factor to a new phone number; omit the body entirely otherwise. - properties: - mobile: - type: object - description: For an `SMS_OTP` reset, the new mobile number to enroll as the customer completes the reset. Required for an `SMS_OTP` reset; ignored for `TOTP` / `PASSKEY` resets. - required: - - countryCode - - number - properties: - countryCode: - type: string - description: The country dialing code, including the leading `+`. - example: '+1' - number: - type: string - description: The national subscriber number, without the country code. - example: '4155550123' - ScaChallenge: + format: uri + description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. + example: https://app.example.com/onboarding/completed + KycProvider: + type: string + description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. + enum: + - SUMSUB + example: SUMSUB + KycLinkResponse: type: object - description: |- - A Strong Customer Authentication challenge that must be satisfied before a money-movement operation can complete. This object is **only present when the customer is in a region where SCA is required** (the EU); for customers outside SCA-regulated regions it is omitted entirely and no action is needed. - - When present on a quote, authorize it by submitting an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize`. - - **A single operation may require more than one authorization, in sequence.** Treat `scaChallenge` as *the challenge to satisfy now*, not "the only one". After each authorize, re-inspect the returned quote/transaction: if it is still `PENDING_AUTHORIZATION`, it carries the **next** `scaChallenge` (a new `id`) — authorize that too, and repeat until it leaves `PENDING_AUTHORIZATION`. Do not assume one authorization releases the transfer. The number of authorizations is flow-dependent and **may decrease in future**: for example, a cross-currency send today authorizes the currency conversion and the payout as two separate challenges; a future update may collapse them into one. A client written to loop on status handles any count unchanged. + description: A hosted KYC link that the customer can complete to verify their identity. required: - - id + - kycUrl - expiresAt - - factor - - availableFactors + - provider properties: - id: + kycUrl: type: string - description: Unique identifier for this challenge. The server resolves the active challenge from the quote or transaction being authorized, so this field need not be supplied back; it is informational (e.g. for logging or correlation). - example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007 + description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. + example: https://kyc.lightspark.com/onboard/abc123def456 expiresAt: type: string format: date-time - description: Absolute UTC timestamp after which this challenge can no longer be authorized. - example: '2025-10-03T12:05:00Z' - factor: - $ref: '#/components/schemas/ScaFactor' - description: The factor this challenge was issued for. Defaults to `SMS_OTP`. - availableFactors: - type: array - description: The factors the customer may use to satisfy this challenge. - items: - $ref: '#/components/schemas/ScaFactor' - example: - - SMS_OTP - purpose: - type: - - string - - 'null' - description: Optional, informational label for what this particular challenge in the sequence authorizes — useful for step UX (e.g. "Authorize the currency conversion" vs "Authorize the payout"). Known values include `CURRENCY_CONVERSION`, `PAYOUT`, and `TRANSFER`, but the set is **non-exhaustive and may grow** — treat unrecognized values as a generic authorization step and do not branch program logic on it. Omitted when steps are not distinguished (e.g. a single-authorization flow). - example: PAYOUT - passkeyAssertionOptions: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion request options (including the relying-party id, challenge, and allowed credentials), present only when `factor` is `PASSKEY`. Pass to the device's WebAuthn API to produce the assertion submitted back in `ScaAuthorization.passkeyAssertion`. - passkeyAllowedOrigins: - type: - - array - - 'null' - items: - type: string - description: The origins the WebAuthn ceremony may run against. Populated for enrollment and login passkey challenges; the origin the assertion is produced against must be one of these and echoed back as `ScaAuthorization.origin`. Per-transaction passkey challenges omit this (they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin` for how to source the origin in that case. - example: - - https://app.example.com - BeneficiaryTrustStart: - type: object - description: The SCA challenge (if any) a caller authorizes to finish trusting (or untrusting) a beneficiary. The beneficiary is identified by its `externalAccountId`, so the confirm call needs no separate handle. `scaChallenge` is omitted when no challenge is issued; the caller then confirms without a `challengeId`. - properties: - scaChallenge: - $ref: '#/components/schemas/ScaChallenge' - description: The SCA challenge to satisfy on the confirm call. Omitted when no challenge is issued. - BeneficiaryTrustConfirmRequest: + description: Time at which the hosted link expires and can no longer be used. + example: '2027-01-15T14:32:00Z' + provider: + $ref: '#/components/schemas/KycProvider' + token: + type: string + description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. + example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ContactVerificationConfirmRequest: type: object - description: Confirms trusting or untrusting a beneficiary by submitting the SCA proof. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`) and, when the start issued one, the `challengeId`. The beneficiary is identified by the `externalAccountId` in the path — no separate handle is needed. - anyOf: - - required: - - code - - required: - - passkeyAssertion - - origin + description: Request body for confirming an email or phone verification challenge. + required: + - code properties: - challengeId: - type: - - string - - 'null' - description: The challenge handle from the trust start's `scaChallenge`, when one was issued. Omit when the start returned no challenge. code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. + type: string + description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the challenge's assertion options. Required when satisfying a `PASSKEY` challenge. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against. Required alongside `passkeyAssertion`; omit it for the `code` path. - example: https://app.example.com - BeneficiaryTrustConfirm: - type: object - description: The result of a confirm-trust / confirm-untrust call. `trusted` is `true` after a successful trust and `false` after a successful untrust. - required: - - trusted - properties: - trusted: - type: boolean - description: Whether the beneficiary is now trusted. `true` after a successful trust, `false` after a successful untrust. InternalAccountType: title: Internal Account Type type: string @@ -13551,6 +11001,19 @@ components: - `FROZEN`: The account cannot send or receive payments. Grid may freeze an account in response to compliance or fraud signals; payments are blocked while the account remains frozen. - `FAILED`: The account could not be provisioned. Grid was unable to create the underlying account, so it cannot send or receive payments and requires remediation. example: ACTIVE + CurrencyAmount: + type: object + required: + - amount + - currency + properties: + amount: + type: integer + format: int64 + description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) + example: 12550 + currency: + $ref: '#/components/schemas/Currency' PaymentAccountType: type: string enum: @@ -13576,12 +11039,10 @@ components: - LIGHTNING - SOLANA_WALLET - TRON_WALLET - - PLASMA_WALLET - POLYGON_WALLET - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET - - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -13614,43 +11075,10 @@ components: minLength: 9 maxLength: 9 pattern: ^[0-9]{9}$ - bankName: - type: string - description: The name of the financial institution holding the account. Optional on every rail, and recommended for wires, where it identifies the beneficiary's institution on the payment message. - example: Chase Bank - minLength: 1 - maxLength: 140 - bankAccountType: - type: string - description: Whether the account is a checking or a savings account. Optional on every rail; when omitted, the account is treated as a checking account. - enum: - - CHECKING - - SAVINGS - example: CHECKING - intermediaryBankName: - type: string - description: The name of the intermediary financial institution, for accounts reachable only through a correspondent bank. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: JPMorgan Chase Bank - minLength: 1 - maxLength: 140 - intermediaryRoutingNumber: - type: string - description: The ABA routing number of the intermediary financial institution. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: '021000021' - minLength: 9 - maxLength: 9 - pattern: ^[0-9]{9}$ - fiToFiInformation: - type: string - description: Bank-to-bank instructions carried alongside the payment. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. - example: /BNF/Invoice 4471 - maxLength: 210 example: accountType: USD_ACCOUNT accountNumber: '1234567890' routingNumber: '021000021' - bankName: Chase Bank - bankAccountType: CHECKING UsdAccountInfo: allOf: - $ref: '#/components/schemas/UsdAccountInfoBase' @@ -14623,32 +12051,6 @@ components: description: Type of asset enum: - USDT - PlasmaWalletInfo: - type: object - required: - - address - - accountType - properties: - accountType: - type: string - enum: - - PLASMA_WALLET - address: - type: string - description: Plasma wallet address - example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - PaymentPlasmaWalletInfo: - title: Plasma Wallet - allOf: - - $ref: '#/components/schemas/BasePaymentAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletInfo' - - type: object - properties: - assetType: - type: string - description: Type of asset - enum: - - USDT PolygonWalletInfo: type: object required: @@ -14727,29 +12129,6 @@ components: description: Type of asset enum: - USDC - - USDT - PaymentBitcoinDepositAddressInfo: - title: Bitcoin L1 Deposit Address - allOf: - - $ref: '#/components/schemas/BasePaymentAccountInfo' - - type: object - required: - - address - properties: - accountType: - type: string - enum: - - BITCOIN_L1 - address: - type: string - description: On-chain Bitcoin (L1) deposit address to send funds to - example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq - network: - type: string - description: The blockchain network for the deposit address. - enum: - - BITCOIN - example: BITCOIN AedAccountInfoBase: type: object required: @@ -16127,11 +13506,9 @@ components: - $ref: '#/components/schemas/PaymentLightningInvoiceInfo' - $ref: '#/components/schemas/PaymentSolanaWalletInfo' - $ref: '#/components/schemas/PaymentTronWalletInfo' - - $ref: '#/components/schemas/PaymentPlasmaWalletInfo' - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' - - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -16156,11 +13533,9 @@ components: LIGHTNING: '#/components/schemas/PaymentLightningInvoiceInfo' SOLANA_WALLET: '#/components/schemas/PaymentSolanaWalletInfo' TRON_WALLET: '#/components/schemas/PaymentTronWalletInfo' - PLASMA_WALLET: '#/components/schemas/PaymentPlasmaWalletInfo' POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' - BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -16342,7 +13717,6 @@ components: - BASE_WALLET - ETHEREUM_WALLET - LIGHTNING - - PLASMA_WALLET - POLYGON_WALLET - SOLANA_WALLET - SPARK_WALLET @@ -18448,11 +15822,6 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletInfo' - PlasmaWalletExternalAccountInfo: - title: Plasma Wallet - allOf: - - $ref: '#/components/schemas/BaseExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletInfo' SolanaWalletExternalAccountInfo: title: Solana Wallet allOf: @@ -18562,7 +15931,6 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -18612,7 +15980,6 @@ components: LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' LIGHTNING_ACCOUNT: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' - PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -19482,7 +16849,6 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' - - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -19531,7 +16897,6 @@ components: ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo' LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' - PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -20024,7 +17389,6 @@ components: enum: - CREATED - PENDING - - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - REJECTED @@ -20038,7 +17402,6 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -20078,7 +17441,6 @@ components: - BASE - POLYGON - TRON - - PLASMA - SPARK description: The blockchain network an on-chain transaction settled on. Whether this is the mainnet or a test network (e.g. Solana devnet) is determined by your platform's environment — sandbox platforms operate on test networks, production platforms on mainnet — mirroring how `cryptoNetwork` is interpreted elsewhere in the API. example: SOLANA @@ -20143,43 +17505,6 @@ components: mapping: ACCOUNT: '#/components/schemas/AccountTransactionDestination' UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionDestination' - Refund: - type: object - required: - - reference - - initiatedAt - - status - properties: - reference: - type: string - description: The unique reference ID of the refund - example: UMA-Q12345-REFUND - initiatedAt: - type: string - format: date-time - description: When the refund was initiated - example: '2025-08-15T14:30:00Z' - settledAt: - type: string - format: date-time - description: When the refund was settled - example: '2025-08-15T14:35:00Z' - status: - type: string - enum: - - PENDING - - COMPLETED - - FAILED - description: Current status of the refund - example: COMPLETED - reason: - type: string - enum: - - TRANSACTION_FAILED - - USER_CANCELLATION - - TIMEOUT - description: Reason for the refund - example: TRANSACTION_FAILED CounterpartyInformation: type: object description: Additional information about the counterparty, if available and relevant to the transaction and platform. @@ -20214,11 +17539,11 @@ components: $ref: '#/components/schemas/TransactionDestinationOneOf' customerId: type: string - description: System ID of the customer this transaction belongs to + description: System ID of the customer (sender for outgoing, recipient for incoming) example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific ID of the customer this transaction belongs to + description: Platform-specific ID of the customer (sender for outgoing, recipient for incoming) example: 18d3e5f7b4a9c2 settledAt: type: string @@ -20248,21 +17573,6 @@ components: type: string description: Optional memo or description for the payment example: 'Payment for invoice #1234' - sentAmount: - $ref: '#/components/schemas/CurrencyAmount' - description: Amount sent in the sender's currency - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 1.08 - quoteId: - type: string - description: The ID of the quote that was used to trigger this payment - example: Quote:019542f5-b3e7-1d02-0000-000000000006 - refund: - $ref: '#/components/schemas/Refund' - description: The refund if transaction was refunded. counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' TransactionSourceType: @@ -20404,6 +17714,39 @@ components: type: string description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' + IncomingRateDetails: + description: 'Details about the rate and fees for an incoming transaction. Note: `gridApiFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' + type: object + required: + - gridApiMultiplier + - gridApiFixedFee + - gridApiVariableFeeRate + - gridApiVariableFeeAmount + properties: + gridApiMultiplier: + type: number + format: double + description: The underlying multiplier from the mSATS to the receiving currency, including variable fees. + exclusiveMinimum: 0 + example: 0.925 + gridApiFixedFee: + type: integer + format: int64 + description: The fixed fee charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). + minimum: 0 + example: 10 + gridApiVariableFeeRate: + type: number + format: double + description: The variable fee rate charged by the Grid product to execute the quote as a percentage of the receiving currency amount. + minimum: 0 + example: 0.003 + gridApiVariableFeeAmount: + type: number + format: int64 + description: The variable fee amount charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). This is the receiving amount times gridApiVariableFeeRate. + minimum: 0 + example: 30 IncomingTransactionFailureReason: type: string enum: @@ -20437,12 +17780,15 @@ components: fees: type: integer format: int64 - description: The total fees available from the receive quote in the smallest unit of the sending currency (eg. cents). + description: The total fees available from the receive quote in the smallest unit of the receiving currency (eg. cents). minimum: 0 example: 10 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Included for all transactions except those with "CREATED" status + rateDetails: + $ref: '#/components/schemas/IncomingRateDetails' + description: Details about the rate and fees for the transaction. failureReason: $ref: '#/components/schemas/IncomingTransactionFailureReason' description: If the transaction failed, this field provides the reason for failure. @@ -20450,7 +17796,6 @@ components: type: string enum: - PENDING - - PENDING_AUTHORIZATION - EXPIRED - PROCESSING - COMPLETED @@ -20461,11 +17806,47 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | | `FAILED` | Something went wrong — accompanied by a `failureReason` | + Refund: + type: object + required: + - reference + - initiatedAt + - status + properties: + reference: + type: string + description: The unique reference ID of the refund + example: UMA-Q12345-REFUND + initiatedAt: + type: string + format: date-time + description: When the refund was initiated + example: '2025-08-15T14:30:00Z' + settledAt: + type: string + format: date-time + description: When the refund was settled + example: '2025-08-15T14:35:00Z' + status: + type: string + enum: + - PENDING + - COMPLETED + - FAILED + description: Current status of the refund + example: COMPLETED + reason: + type: string + enum: + - TRANSACTION_FAILED + - USER_CANCELLATION + - TIMEOUT + description: Reason for the refund + example: TRANSACTION_FAILED OutgoingRateDetails: description: 'Details about the rate and fees for an outgoing transaction or quote. Note: `counterpartyFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' type: object @@ -20521,15 +17902,7 @@ components: - LIGHTNING_PAYMENT_FAILED - FUNDING_AMOUNT_MISMATCH - COUNTERPARTY_POST_TX_FAILED - - SCA_NOT_COMPLETED - - EXECUTION_FAILED_POST_DEBIT - - SETTLEMENT_FAILED - - TIMEOUT - - MANUAL_REFUND - description: |- - Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. - - `SCA_NOT_COMPLETED` means the customer did not satisfy the Strong Customer Authentication challenge before it expired, so the transaction was never authorized and no funds were moved. Only occurs for customers in a region where SCA is required (e.g. the EU). Create a new quote to try again, and have the customer authorize it while the challenge is live. + description: Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. RailSelectionMode: type: string enum: @@ -20561,21 +17934,24 @@ components: receivedAmount: $ref: '#/components/schemas/CurrencyAmount' description: Amount to be received by recipient in the recipient's currency + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 1.08 fees: type: integer format: int64 description: The fees associated with the quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 - platformFees: - type: integer - format: int64 - description: The portion of `fees` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `fees`. - minimum: 0 - example: 5 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Reconciliation details for this transaction. For the on-chain hash of a crypto payout to an external wallet, see the destination's `onChainTransaction` instead. + quoteId: + type: string + description: The ID of the quote that was used to trigger this payment + example: Quote:019542f5-b3e7-1d02-0000-000000000006 paymentInstructions: type: array description: Payment instructions for executing the payment. @@ -20597,6 +17973,9 @@ components: assetType: BTC address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + refund: + $ref: '#/components/schemas/Refund' + description: The refund if transaction was refunded. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. @@ -20823,24 +18202,6 @@ components: description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. allOf: - $ref: '#/components/schemas/PaymentRail' - PurposeOfPayment: - type: string - description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). - enum: - - GIFT - - SELF - - GOODS_OR_SERVICES - - EDUCATION - - HEALTH_OR_MEDICAL - - REAL_ESTATE_PURCHASE - - TAX_PAYMENT - - LOAN_PAYMENT - - UTILITY_BILL - - DONATION - - TRAVEL - - FAMILY_SUPPORT - - SALARY_PAYMENT - - OTHER TransferOutRequest: type: object required: @@ -20863,8 +18224,6 @@ components: maxLength: 80 description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' example: '12345' - purposeOfPayment: - $ref: '#/components/schemas/PurposeOfPayment' CurrencyPreference: type: object required: @@ -21124,12 +18483,11 @@ components: type: string enum: - PENDING - - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - FAILED - EXPIRED - description: 'Current status of the quote. `PENDING_AUTHORIZATION` occurs only for customers in a region where Strong Customer Authentication is required (e.g. EU): the quote carries an `scaChallenge` that must be authorized before execution, and for realtime-funding sources `paymentInstructions` are withheld until it is satisfied.' + description: Current status of the quote example: PENDING createdAt: type: string @@ -21173,12 +18531,6 @@ components: description: 'The fees associated with the quote in the smallest unit of the sending currency (eg. cents). Note: this value may fluctuate between quotes — some underlying fee components are defined in the receiving currency, so their equivalent in the sending currency moves with the FX rate. The fees shown here are locked only for the lifetime of this quote.' minimum: 0 example: 10 - platformFeesIncluded: - type: integer - format: int64 - description: The portion of `feesIncluded` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `feesIncluded`. May be omitted from payloads produced before platform fees existed. - minimum: 0 - example: 5 paymentInstructions: type: array description: Payment instructions for executing the payment. This is not required when using an internal account source. @@ -21204,22 +18556,41 @@ components: type: string description: The ID of the transaction created from this quote. example: Transaction:019542f5-b3e7-1d02-0000-000000000005 + remittanceInformation: + type: string + maxLength: 80 + description: 'Free-form information about the payment that travels with it to the recipient, as provided on the quote request. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' + example: '12345' counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' description: Additional information about the counterparty, if available and required by the platform in their configuration. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. - scaChallenge: - $ref: '#/components/schemas/ScaChallenge' - readOnly: true - description: 'Present only while `status` is `PENDING_AUTHORIZATION`: the Strong Customer Authentication challenge to satisfy before this quote can be executed (or, for realtime-funding sources, before `paymentInstructions` are issued). Omitted for customers outside SCA-regulated regions (non-EU).' QuoteLockSide: type: string enum: - SENDING - RECEIVING description: The side of the quote which should be locked and specified in the `lockedCurrencyAmount`. For example, if I want to send exactly $5 MXN from my wallet, I would set this to "sending", and the `lockedCurrencyAmount` to 500 (in cents). If I want the receiver to receive exactly $10 USD, I would set this to "receiving" and the `lockedCurrencyAmount` to 10000 (in cents). + PurposeOfPayment: + type: string + description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). + enum: + - GIFT + - SELF + - GOODS_OR_SERVICES + - EDUCATION + - HEALTH_OR_MEDICAL + - REAL_ESTATE_PURCHASE + - TAX_PAYMENT + - LOAN_PAYMENT + - UTILITY_BILL + - DONATION + - TRAVEL + - FAMILY_SUPPORT + - SALARY_PAYMENT + - OTHER QuoteRequest: type: object required: @@ -21265,9 +18636,6 @@ components: example: '12345' purposeOfPayment: $ref: '#/components/schemas/PurposeOfPayment' - scaFactor: - $ref: '#/components/schemas/ScaFactor' - description: Optional preferred factor for a Strong Customer Authentication challenge issued at quote creation. Only relevant for a realtime-funding source in a region where SCA is required (e.g. EU); ignored otherwise. Valid values are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected. When the quote is returned in `PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`. senderCustomerInfo: type: object additionalProperties: true @@ -21277,41 +18645,6 @@ components: example: FULL_NAME: Jane Receiver NATIONALITY: FR - ExecuteQuoteRequest: - type: object - description: Optional body for executing a quote. Only needed to request a specific Strong Customer Authentication factor (`scaFactor`) for the challenge this call issues; omit the body entirely otherwise. - properties: - scaFactor: - $ref: '#/components/schemas/ScaFactor' - description: Optional preferred factor for the Strong Customer Authentication challenge this call issues. Only relevant for customers in a region where SCA is required (e.g. EU); ignored otherwise. Valid values for a per-transaction challenge are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected here. Omit to default to `SMS_OTP`. - ScaAuthorization: - type: object - description: Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for `SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a `passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound to the origin it was produced against, and a passkey confirmation is rejected without it. - properties: - code: - type: - - string - - 'null' - description: The one-time code the customer received by SMS, or read from their authenticator app. In sandbox, the code is always `123456`. - example: '123456' - passkeyAssertion: - type: - - object - - 'null' - additionalProperties: true - description: Opaque WebAuthn assertion produced by the device from the challenge's `passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge. - origin: - type: - - string - - 'null' - description: The WebAuthn origin the `passkeyAssertion` was produced against. **Required** alongside `passkeyAssertion`; omit it for the `code` path. When the challenge lists `passkeyAllowedOrigins` (enrollment / login challenges), it must be one of those. A per-transaction passkey challenge carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in that case supply the origin your app invoked the WebAuthn API from, which must match the relying party in `passkeyAssertionOptions`. - example: https://app.example.com - endUserIpAddress: - type: - - string - - 'null' - description: 'The IP address of the end user''s device authorizing this operation, forwarded to the SCA provider where it feeds risk assessment and any transaction-risk exemption. Supply the customer''s address, not your server''s. Optional: the provider records it for money-movement authorizations, and ignores it for beneficiary trust changes.' - example: 203.0.113.42 TransactionListResponse: type: object required: @@ -21354,13 +18687,6 @@ components: type: string description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes. example: RESTRICTED_JURISDICTION - CancelTransactionRequest: - type: object - properties: - reason: - type: string - description: Optional reason for cancelling the transaction. This is just for debugging purposes or can be used for a platform's own purposes. - example: REQUESTED_AFTER_HOURS EstimateCryptoWithdrawalFeeRequest: type: object required: @@ -22291,6 +19617,33 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the `encryptedSessionSigningKey` must not be used to sign further requests. example: '2026-04-09T15:30:01Z' + Error429: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 429 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | + enum: + - RATE_LIMITED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. @@ -23148,174 +20501,592 @@ components: description: Indicates if more results are available beyond this page nextCursor: type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of cards matching the criteria (excluding pagination) + CardCreateRequest: + type: object + required: + - cardholderId + - form + - fundingSources + properties: + cardholderId: + type: string + description: The id of the `Customer` to issue the card to. The customer must have KYC status `APPROVED`; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: + type: string + description: Optional platform-specific card identifier. System-generated when omitted, mirroring `platformCustomerId` semantics. + example: card-emp-aary-001 + form: + $ref: '#/components/schemas/CardForm' + fundingSources: + type: array + description: Internal account ids to bind as funding sources, in priority order. The first entry is tried first by Authorization Decisioning. Every card must be bound to at least one source, and every source must belong to the cardholder and be denominated in a card-eligible currency (USDB in v1); otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. + minItems: 1 + items: + type: string + example: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + CardUpdateRequest: + type: object + description: Update request for `PATCH /cards/{id}`. At least one of `state` or `fundingSources` must be supplied. `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`; any other transition returns `409 INVALID_STATE_TRANSITION`. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSources`. `fundingSources`, when supplied, fully replaces the card's bound funding sources — the array order determines the priority Authorization Decisioning tries them in. + properties: + state: + type: string + enum: + - ACTIVE + - FROZEN + - CLOSED + description: Target state for the card. Permitted transitions are `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. + example: FROZEN + fundingSources: + type: array + description: 'New ordered list of internal account ids to bind as funding sources. Fully replaces the previous binding. Each id must belong to the cardholder and be denominated in the card''s currency. The list must contain at least one source — to stop a card from spending without removing all sources, transition it to `FROZEN` instead. Cannot be supplied alongside `state: CLOSED`.' + minItems: 1 + items: + type: string + example: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + CardRevealResponse: + type: object + required: + - panEmbedUrl + - expiresAt + properties: + panEmbedUrl: + type: string + format: uri + description: 'Signed URL of the card processor''s iframe that securely displays the PAN, CVV, and expiry to the cardholder. The full PAN and CVV never cross Grid''s servers — render this URL in an iframe in your client to reveal card details. The URL is a short-lived bearer secret: render it immediately and never store, cache, or log it.' + example: https://embed.lithic.com/iframe/...?t=... + expiresAt: + type: string + format: date-time + description: When the signed URL stops loading. Request a new reveal rather than re-rendering an expired URL. + example: '2026-05-08T14:16:00Z' + SandboxCardAuthorizationRequest: + type: object + required: + - amount + - currency + - merchant + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the endpoint documentation for the suffix table. + properties: + amount: + type: integer + format: int64 + description: Authorization amount in the smallest unit of `currency` (e.g. cents for USD). + exclusiveMinimum: 0 + example: 1250 + currency: + $ref: '#/components/schemas/Currency' + merchant: + $ref: '#/components/schemas/CardMerchant' + SandboxCardClearingRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/clearing`. Drives a clearing event against an existing `CardTransaction`. Pass an `amount` greater than the authorized amount to exercise the over-auth / restaurant-tip post-hoc-pull path; pass `0` to exercise `AUTHORIZATION_EXPIRY`. Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to clear against. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: Clearing amount in the smallest unit of the transaction's currency. Set to `0` to simulate an authorization expiry with no clearing. + minimum: 0 + example: 1500 + SandboxCardReturnRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/return`. Drives a `RETURN` event against an existing settled `CardTransaction`, which creates a `CardRefund` and pushes the parent transaction towards `REFUNDED` (full) or keeps it `SETTLED` (partial). + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to refund against. Must have at least one settled clearing. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). + exclusiveMinimum: 0 + example: 1500 + StablecoinIssuanceStatus: + type: string + enum: + - PENDING_REVIEW + - PROVISIONING + - PROVISIONED + - REJECTED + - FAILED + description: Provider issuance/linking status for the registered stablecoin. + StablecoinGridOperationsStatus: + type: string + enum: + - NOT_ENABLED + - PENDING_APPROVAL + - ENABLING + - ENABLED + - DISABLED + description: Whether the stablecoin has been enabled for normal Grid operations. + StablecoinProvider: + type: string + enum: + - BRALE + description: Stablecoin provider backing the linked account, stablecoin, or operation. + StablecoinNetwork: + type: string + enum: + - SPARK + description: Public network family for the stablecoin token identifier. The concrete Spark network is inferred from the platform mode. + StablecoinProviderEnvironment: + type: string + enum: + - SANDBOX + - PRODUCTION + description: Provider environment derived from the authenticated Grid platform mode. + Stablecoin: + type: object + required: + - id + - name + - symbol + - baseCurrency + - network + - decimals + - issuanceStatus + - gridOperationsStatus + - networkTokenIdentifier + - provider + - stablecoinProviderAccountId + - providerEnvironment + - providerTokenIdentifier + - createdAt + - updatedAt + properties: + id: + type: string + description: System-generated stablecoin identifier. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + name: + type: string + description: Stablecoin display name. + example: Acme Dollar + symbol: + type: string + description: Stablecoin symbol. + example: ACME + baseCurrency: + type: string + description: Fiat currency the stablecoin is denominated against. + example: USD + network: + $ref: '#/components/schemas/StablecoinNetwork' + decimals: + type: integer + description: Number of decimal places used by the stablecoin. + example: 6 + issuanceStatus: + $ref: '#/components/schemas/StablecoinIssuanceStatus' + gridOperationsStatus: + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + networkTokenIdentifier: + type: string + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + currency: + type: string + description: Grid currency code after the stablecoin is enabled for Grid operations. + example: ACME + provider: + $ref: '#/components/schemas/StablecoinProvider' + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link selected for this stablecoin. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + providerEnvironment: + $ref: '#/components/schemas/StablecoinProviderEnvironment' + providerTokenIdentifier: + type: string + description: Provider token identifier. For Brale this maps to `value_type`. + example: ACME + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' + StablecoinListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + items: + $ref: '#/components/schemas/Stablecoin' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: + type: string + description: Cursor to retrieve the next page of results. totalCount: type: integer - description: Total number of cards matching the criteria (excluding pagination) - CardCreateRequest: + description: Total number of stablecoins matching the criteria. + StablecoinRegisterRequest: type: object required: - - cardholderId - - form - - fundingSources + - providerTokenIdentifier + - networkTokenIdentifier + - network properties: - cardholderId: + providerTokenIdentifier: type: string - description: The id of the `Customer` to issue the card to. The customer must have KYC status `APPROVED`; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: + description: Provider identifier for the stablecoin. For Brale this maps to `value_type`. + example: ACME + networkTokenIdentifier: type: string - description: Optional platform-specific card identifier. System-generated when omitted, mirroring `platformCustomerId` semantics. - example: card-emp-aary-001 - form: - $ref: '#/components/schemas/CardForm' - fundingSources: - type: array - description: Internal account ids to bind as funding sources, in priority order. The first entry is tried first by Authorization Decisioning. Every card must be bound to at least one source, and every source must belong to the cardholder and be denominated in a card-eligible currency (USDB in v1); otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. - minItems: 1 - items: - type: string - example: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - CardUpdateRequest: + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + network: + $ref: '#/components/schemas/StablecoinNetwork' + stablecoinProviderAccountId: + type: string + description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + StablecoinMintFundingSource: type: object - description: Update request for `PATCH /cards/{id}`. At least one of `state` or `fundingSources` must be supplied. `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`; any other transition returns `409 INVALID_STATE_TRANSITION`. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSources`. `fundingSources`, when supplied, fully replaces the card's bound funding sources — the array order determines the priority Authorization Decisioning tries them in. + required: + - type properties: - state: + type: type: string enum: - - ACTIVE - - FROZEN - - CLOSED - description: Target state for the card. Permitted transitions are `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. - example: FROZEN - fundingSources: - type: array - description: 'New ordered list of internal account ids to bind as funding sources. Fully replaces the previous binding. Each id must belong to the cardholder and be denominated in the card''s currency. The list must contain at least one source — to stop a card from spending without removing all sources, transition it to `FROZEN` instead. Cannot be supplied alongside `state: CLOSED`.' - minItems: 1 - items: - type: string - example: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - CardRevealResponse: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. + externalAccountId: + type: string + description: Grid `ExternalAccount` used as the ACH debit funding account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + accountId: + type: string + description: Grid internal funding account id for future Grid-funded mint flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type for future provider internal balance funding. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for future provider internal balance funding. + example: SOLANA + StablecoinMintDestination: type: object required: - - panEmbedUrl - - expiresAt + - type properties: - panEmbedUrl: + type: type: string - format: uri - description: 'Signed URL of the card processor''s iframe that securely displays the PAN, CVV, and expiry to the cardholder. The full PAN and CVV never cross Grid''s servers — render this URL in an iframe in your client to reveal card details. The URL is a short-lived bearer secret: render it immediately and never store, cache, or log it.' - example: https://embed.lithic.com/iframe/...?t=... - expiresAt: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. + externalAccountId: type: string - format: date-time - description: When the signed URL stops loading. Request a new reveal rather than re-rendering an expired URL. - example: '2026-05-08T14:16:00Z' - SandboxCardAuthorizationRequest: + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + accountId: + type: string + description: Grid internal receiving account id for future Grid-managed mint destinations. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + StablecoinMintRequest: type: object required: - amount - - currency - - merchant - description: 'Sandbox-only request body shared by the card authorization-family simulate endpoints: `simulate/authorization`, `simulate/credit_authorization`, `simulate/financial_authorization`, `simulate/financial_credit_authorization`, and `simulate/credit_authorization_advice`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` documentation for the suffix table.' + - fundingSource + - destination properties: amount: - type: integer - format: int64 - description: Authorization amount in the smallest unit of `currency` (e.g. cents for USD). - exclusiveMinimum: 0 - example: 1250 - currency: - $ref: '#/components/schemas/Currency' - merchant: - $ref: '#/components/schemas/CardMerchant' - SandboxCardSimulationResponse: + type: string + pattern: ^[0-9]+$ + description: Amount to mint in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + fundingSource: + $ref: '#/components/schemas/StablecoinMintFundingSource' + destination: + $ref: '#/components/schemas/StablecoinMintDestination' + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Initial program funding mint + StablecoinOperationType: + type: string + enum: + - MINT + - BURN + description: Stablecoin issuer operation type. + StablecoinOperationStatus: + type: string + enum: + - CREATED + - PENDING_FUNDING + - PENDING_PROVIDER + - PROCESSING + - COMPLETED + - FAILED + - EXPIRED + description: Stablecoin issuer operation lifecycle status. + StablecoinBurnSource: type: object required: - - issuerTransactionToken - description: Response body for the sandbox card-event simulators. The simulate call pokes the card issuer's sandbox; the resulting card operation is delivered asynchronously via the issuer's events webhook, never synchronously in this response. + - type properties: - issuerTransactionToken: + type: type: string - description: The card issuer's transaction token for the simulated event. Correlates the eventual webhook-delivered card operation back to this simulate call. - example: f3a1c2d4-5b6e-7890-abcd-ef0123456789 - SandboxCardClearingRequest: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + accountId: + type: string + description: Grid internal source account id for future Grid-controlled burn flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + StablecoinFundingInstructions: type: object + description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. + additionalProperties: true + example: + rail: SPARK + network: SPARK + address: spark1... + valueType: ACME + StablecoinOperationDestination: + type: object + description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint and burn destination request variants) so the field is unambiguously deserializable regardless of operation type: `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' required: - - cardTransactionId - - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/clearing`. Drives a clearing event against an existing `CardTransaction`. Pass an `amount` greater than the authorized amount to exercise the over-auth / restaurant-tip post-hoc-pull path; pass `0` to exercise `AUTHORIZATION_EXPIRY`. Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds. + - type properties: - cardTransactionId: + type: type: string - description: The id of the `CardTransaction` to clear against. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: - type: integer - format: int64 - description: Clearing amount in the smallest unit of the transaction's currency. Set to `0` to simulate an authorization expiry with no clearing. - minimum: 0 - example: 1500 - SandboxCardReturnRequest: + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Destination variant. + externalAccountId: + type: string + description: Grid `ExternalAccount` id, when the destination is an external account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + accountId: + type: string + description: Grid internal account id, when the destination is a Grid-managed account. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail, present for burn (fiat redemption) destinations. + StablecoinEstimatedDelivery: + type: object + description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. + additionalProperties: true + example: + rail: ACH_CREDIT + amount: '1.00' + currency: USD + timing: 1-3 business days + StablecoinOperation: type: object required: - - cardTransactionId + - id + - stablecoinId + - operationType + - status - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/return`. Drives a `RETURN` event against an existing settled `CardTransaction`, which creates a `CardRefund` and pushes the parent transaction towards `REFUNDED` (full) or keeps it `SETTLED` (partial). + - provider + - providerEnvironment + - stablecoinProviderAccountId + - createdAt + - updatedAt properties: - cardTransactionId: + id: type: string - description: The id of the `CardTransaction` to refund against. Must have at least one settled clearing. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + description: System-generated stablecoin issuer operation identifier. + example: StablecoinOperation:019542f5-b3e7-1d02-0000-000000000301 + stablecoinId: + type: string + description: Stablecoin this operation belongs to. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + operationType: + $ref: '#/components/schemas/StablecoinOperationType' + status: + $ref: '#/components/schemas/StablecoinOperationStatus' amount: + type: string + description: Operation amount in the stablecoin's smallest unit. + example: '1000000' + provider: + $ref: '#/components/schemas/StablecoinProvider' + providerEnvironment: + $ref: '#/components/schemas/StablecoinProviderEnvironment' + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link used for the operation. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + fundingSource: + $ref: '#/components/schemas/StablecoinMintFundingSource' + source: + $ref: '#/components/schemas/StablecoinBurnSource' + fundingInstructions: + $ref: '#/components/schemas/StablecoinFundingInstructions' + destination: + $ref: '#/components/schemas/StablecoinOperationDestination' + estimatedDelivery: + $ref: '#/components/schemas/StablecoinEstimatedDelivery' + expiresAt: + type: string + format: date-time + description: Expiry for operations awaiting external funding. + example: '2026-05-27T16:40:00Z' + providerStatus: + type: string + description: Sanitized provider status when available. + example: pending + failureReason: + type: string + description: Stable internal failure code for terminal failed operations. + example: PROVIDER_TRANSFER_FAILED + description: + type: string + description: Platform-provided operation description. + example: Initial program funding mint + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' + Error503: + type: object + required: + - message + - status + - code + properties: + status: type: integer - format: int64 - description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). - exclusiveMinimum: 0 - example: 1500 - SandboxCardBalanceInquiryRequest: + enum: + - 503 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | SERVICE_UNAVAILABLE | Downstream service is temporarily unavailable | + enum: + - SERVICE_UNAVAILABLE + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + StablecoinBurnDestination: type: object required: - - merchant - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/balance_inquiry`. Drives a balance-inquiry authorization against the card. A balance inquiry is always a `0`-amount authorization, so it carries no `amount`. + - type + - externalAccountId + - rail properties: - merchant: - $ref: '#/components/schemas/CardMerchant' - SandboxCardAuthorizationAdviceRequest: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: Burn destination variant. V1 supports fiat Grid `ExternalAccount` destinations. + externalAccountId: + type: string + description: Grid bank `ExternalAccount` receiving fiat redemption proceeds. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail for the burn destination. + StablecoinBurnRequest: type: object required: - - cardTransactionId - amount - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization_advice`. Drives an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization. + - source + - destination properties: - cardTransactionId: - type: string - description: The id of the `CardTransaction` the advice re-sizes. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 amount: - type: integer - format: int64 - description: The new *total* authorized amount, in the smallest unit of the transaction's currency, that the advice re-sizes the authorization to. - exclusiveMinimum: 0 - example: 2000 - SandboxCardTransactionRefRequest: + type: string + pattern: ^[0-9]+$ + description: Amount to burn in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + source: + $ref: '#/components/schemas/StablecoinBurnSource' + destination: + $ref: '#/components/schemas/StablecoinBurnDestination' + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Redeem ACME + StablecoinOperationListResponse: type: object required: - - cardTransactionId - description: Sandbox-only request body for simulate endpoints keyed only by an existing card transaction — currently `POST /sandbox/cards/{id}/simulate/return_reversal`. + - data + - hasMore properties: - cardTransactionId: + data: + type: array + items: + $ref: '#/components/schemas/StablecoinOperation' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: type: string - description: The id of the `CardTransaction` to act against. - example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - StablecoinProvider: - type: string - enum: - - BRALE - description: Stablecoin provider backing the linked account, stablecoin, or operation. + description: Cursor to retrieve the next page of results. + totalCount: + type: integer + description: Total number of stablecoin operations matching the query. StablecoinProviderAccountStatus: type: string enum: @@ -23323,12 +21094,6 @@ components: - INVALID - REVOKED description: Status of the linked stablecoin provider account credentials. - StablecoinProviderEnvironment: - type: string - enum: - - SANDBOX - - PRODUCTION - description: Provider environment derived from the authenticated Grid platform mode. StablecoinProviderAccount: type: object required: @@ -23424,9 +21189,6 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED - - INCOMING_PAYMENT.REFUND_PENDING - - INCOMING_PAYMENT.REFUND_COMPLETED - - INCOMING_PAYMENT.REFUND_FAILED - CUSTOMER.KYC_APPROVED - CUSTOMER.KYC_REJECTED - CUSTOMER.KYC_PENDING @@ -23447,11 +21209,6 @@ components: - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - CARD.FUNDING_SOURCE_CHANGE - - CARD_TRANSACTION.AUTHORIZED - - CARD_TRANSACTION.PARTIALLY_SETTLED - - CARD_TRANSACTION.SETTLED - - CARD_TRANSACTION.REFUNDED - - CARD_TRANSACTION.EXCEPTION - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -23512,9 +21269,6 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED - - INCOMING_PAYMENT.REFUND_PENDING - - INCOMING_PAYMENT.REFUND_COMPLETED - - INCOMING_PAYMENT.REFUND_FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -23557,7 +21311,6 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING - - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED @@ -23676,23 +21429,6 @@ components: type: string enum: - CARD.FUNDING_SOURCE_CHANGE - CardTransactionWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/CardTransaction' - type: - type: string - enum: - - CARD_TRANSACTION.AUTHORIZED - - CARD_TRANSACTION.PARTIALLY_SETTLED - - CARD_TRANSACTION.SETTLED - - CARD_TRANSACTION.REFUNDED - - CARD_TRANSACTION.EXCEPTION requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi/components/schemas/errors/Error503.yaml b/openapi/components/schemas/errors/Error503.yaml new file mode 100644 index 000000000..dd695d51b --- /dev/null +++ b/openapi/components/schemas/errors/Error503.yaml @@ -0,0 +1,26 @@ +type: object +required: + - message + - status + - code +properties: + status: + type: integer + enum: + - 503 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | SERVICE_UNAVAILABLE | Downstream service is temporarily unavailable | + enum: + - SERVICE_UNAVAILABLE + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true diff --git a/openapi/components/schemas/stablecoins/Stablecoin.yaml b/openapi/components/schemas/stablecoins/Stablecoin.yaml new file mode 100644 index 000000000..c8584d881 --- /dev/null +++ b/openapi/components/schemas/stablecoins/Stablecoin.yaml @@ -0,0 +1,74 @@ +type: object +required: + - id + - name + - symbol + - baseCurrency + - network + - decimals + - issuanceStatus + - gridOperationsStatus + - networkTokenIdentifier + - provider + - stablecoinProviderAccountId + - providerEnvironment + - providerTokenIdentifier + - createdAt + - updatedAt +properties: + id: + type: string + description: System-generated stablecoin identifier. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + name: + type: string + description: Stablecoin display name. + example: Acme Dollar + symbol: + type: string + description: Stablecoin symbol. + example: ACME + baseCurrency: + type: string + description: Fiat currency the stablecoin is denominated against. + example: USD + network: + $ref: StablecoinNetwork.yaml + decimals: + type: integer + description: Number of decimal places used by the stablecoin. + example: 6 + issuanceStatus: + $ref: StablecoinIssuanceStatus.yaml + gridOperationsStatus: + $ref: StablecoinGridOperationsStatus.yaml + networkTokenIdentifier: + type: string + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + currency: + type: string + description: Grid currency code after the stablecoin is enabled for Grid operations. + example: ACME + provider: + $ref: StablecoinProvider.yaml + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link selected for this stablecoin. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + providerEnvironment: + $ref: StablecoinProviderEnvironment.yaml + providerTokenIdentifier: + type: string + description: Provider token identifier. For Brale this maps to `value_type`. + example: ACME + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' diff --git a/openapi/components/schemas/stablecoins/StablecoinBurnDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinBurnDestination.yaml new file mode 100644 index 000000000..9fe648759 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinBurnDestination.yaml @@ -0,0 +1,23 @@ +type: object +required: + - type + - externalAccountId + - rail +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: Burn destination variant. V1 supports fiat Grid `ExternalAccount` destinations. + externalAccountId: + type: string + description: Grid bank `ExternalAccount` receiving fiat redemption proceeds. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail for the burn destination. diff --git a/openapi/components/schemas/stablecoins/StablecoinBurnRequest.yaml b/openapi/components/schemas/stablecoins/StablecoinBurnRequest.yaml new file mode 100644 index 000000000..1071ad735 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinBurnRequest.yaml @@ -0,0 +1,20 @@ +type: object +required: + - amount + - source + - destination +properties: + amount: + type: string + pattern: '^[0-9]+$' + description: Amount to burn in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + source: + $ref: StablecoinBurnSource.yaml + destination: + $ref: StablecoinBurnDestination.yaml + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Redeem ACME diff --git a/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml b/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml new file mode 100644 index 000000000..13d766e62 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml @@ -0,0 +1,19 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + accountId: + type: string + description: Grid internal source account id for future Grid-controlled burn flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 diff --git a/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml b/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml new file mode 100644 index 000000000..eeb292e71 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml @@ -0,0 +1,8 @@ +type: object +description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. +additionalProperties: true +example: + rail: ACH_CREDIT + amount: '1.00' + currency: USD + timing: 1-3 business days diff --git a/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml b/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml new file mode 100644 index 000000000..d9184dc0d --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml @@ -0,0 +1,8 @@ +type: object +description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. +additionalProperties: true +example: + rail: SPARK + network: SPARK + address: spark1... + valueType: ACME diff --git a/openapi/components/schemas/stablecoins/StablecoinGridOperationsStatus.yaml b/openapi/components/schemas/stablecoins/StablecoinGridOperationsStatus.yaml new file mode 100644 index 000000000..3c8ac4989 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinGridOperationsStatus.yaml @@ -0,0 +1,8 @@ +type: string +enum: + - NOT_ENABLED + - PENDING_APPROVAL + - ENABLING + - ENABLED + - DISABLED +description: Whether the stablecoin has been enabled for normal Grid operations. diff --git a/openapi/components/schemas/stablecoins/StablecoinIssuanceStatus.yaml b/openapi/components/schemas/stablecoins/StablecoinIssuanceStatus.yaml new file mode 100644 index 000000000..fa65ab5b2 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinIssuanceStatus.yaml @@ -0,0 +1,8 @@ +type: string +enum: + - PENDING_REVIEW + - PROVISIONING + - PROVISIONED + - REJECTED + - FAILED +description: Provider issuance/linking status for the registered stablecoin. diff --git a/openapi/components/schemas/stablecoins/StablecoinListResponse.yaml b/openapi/components/schemas/stablecoins/StablecoinListResponse.yaml new file mode 100644 index 000000000..c1ebd7b2e --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinListResponse.yaml @@ -0,0 +1,18 @@ +type: object +required: + - data + - hasMore +properties: + data: + type: array + items: + $ref: Stablecoin.yaml + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: + type: string + description: Cursor to retrieve the next page of results. + totalCount: + type: integer + description: Total number of stablecoins matching the criteria. diff --git a/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml new file mode 100644 index 000000000..60f50a568 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml @@ -0,0 +1,18 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. + externalAccountId: + type: string + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + accountId: + type: string + description: Grid internal receiving account id for future Grid-managed mint destinations. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 diff --git a/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml new file mode 100644 index 000000000..ce0d30684 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml @@ -0,0 +1,29 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. + externalAccountId: + type: string + description: Grid `ExternalAccount` used as the ACH debit funding account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + accountId: + type: string + description: Grid internal funding account id for future Grid-funded mint flows. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type for future provider internal balance funding. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for future provider internal balance funding. + example: SOLANA diff --git a/openapi/components/schemas/stablecoins/StablecoinMintRequest.yaml b/openapi/components/schemas/stablecoins/StablecoinMintRequest.yaml new file mode 100644 index 000000000..7979864f0 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinMintRequest.yaml @@ -0,0 +1,20 @@ +type: object +required: + - amount + - fundingSource + - destination +properties: + amount: + type: string + pattern: '^[0-9]+$' + description: Amount to mint in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals. + example: '1000000' + fundingSource: + $ref: StablecoinMintFundingSource.yaml + destination: + $ref: StablecoinMintDestination.yaml + description: + type: string + maxLength: 1024 + description: Optional platform-provided operation description. + example: Initial program funding mint diff --git a/openapi/components/schemas/stablecoins/StablecoinNetwork.yaml b/openapi/components/schemas/stablecoins/StablecoinNetwork.yaml new file mode 100644 index 000000000..1f1e01760 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinNetwork.yaml @@ -0,0 +1,4 @@ +type: string +enum: + - SPARK +description: Public network family for the stablecoin token identifier. The concrete Spark network is inferred from the platform mode. diff --git a/openapi/components/schemas/stablecoins/StablecoinOperation.yaml b/openapi/components/schemas/stablecoins/StablecoinOperation.yaml new file mode 100644 index 000000000..1a0c28f5f --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperation.yaml @@ -0,0 +1,74 @@ +type: object +required: + - id + - stablecoinId + - operationType + - status + - amount + - provider + - providerEnvironment + - stablecoinProviderAccountId + - createdAt + - updatedAt +properties: + id: + type: string + description: System-generated stablecoin issuer operation identifier. + example: StablecoinOperation:019542f5-b3e7-1d02-0000-000000000301 + stablecoinId: + type: string + description: Stablecoin this operation belongs to. + example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002 + operationType: + $ref: StablecoinOperationType.yaml + status: + $ref: StablecoinOperationStatus.yaml + amount: + type: string + description: Operation amount in the stablecoin's smallest unit. + example: '1000000' + provider: + $ref: StablecoinProvider.yaml + providerEnvironment: + $ref: StablecoinProviderEnvironment.yaml + stablecoinProviderAccountId: + type: string + description: Stablecoin provider account link used for the operation. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 + fundingSource: + $ref: StablecoinMintFundingSource.yaml + source: + $ref: StablecoinBurnSource.yaml + fundingInstructions: + $ref: StablecoinFundingInstructions.yaml + destination: + $ref: StablecoinOperationDestination.yaml + estimatedDelivery: + $ref: StablecoinEstimatedDelivery.yaml + expiresAt: + type: string + format: date-time + description: Expiry for operations awaiting external funding. + example: '2026-05-27T16:40:00Z' + providerStatus: + type: string + description: Sanitized provider status when available. + example: pending + failureReason: + type: string + description: Stable internal failure code for terminal failed operations. + example: PROVIDER_TRANSFER_FAILED + description: + type: string + description: Platform-provided operation description. + example: Initial program funding mint + createdAt: + type: string + format: date-time + description: Creation timestamp. + example: '2026-05-20T16:40:00Z' + updatedAt: + type: string + format: date-time + description: Last update timestamp. + example: '2026-05-20T17:10:00Z' diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml new file mode 100644 index 000000000..908bc02ec --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml @@ -0,0 +1,33 @@ +type: object +description: >- + Destination of a stablecoin operation, as reported on a + `StablecoinOperation`. A single merged shape (superset of the + mint and burn destination request variants) so the field is + unambiguously deserializable regardless of operation type: `rail` + is present for burn (fiat payout) destinations and absent for mint + destinations. +required: + - type +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Destination variant. + externalAccountId: + type: string + description: Grid `ExternalAccount` id, when the destination is an external account. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 + accountId: + type: string + description: Grid internal account id, when the destination is a Grid-managed account. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + rail: + type: string + enum: + - WIRE + - ACH_CREDIT + - SAME_DAY_ACH_CREDIT + - RTP_CREDIT + description: Fiat payout rail, present for burn (fiat redemption) destinations. diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationListResponse.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationListResponse.yaml new file mode 100644 index 000000000..5a8277bdd --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperationListResponse.yaml @@ -0,0 +1,18 @@ +type: object +required: + - data + - hasMore +properties: + data: + type: array + items: + $ref: StablecoinOperation.yaml + hasMore: + type: boolean + description: Indicates if more results are available beyond this page. + nextCursor: + type: string + description: Cursor to retrieve the next page of results. + totalCount: + type: integer + description: Total number of stablecoin operations matching the query. diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationStatus.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationStatus.yaml new file mode 100644 index 000000000..e4b2e803a --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperationStatus.yaml @@ -0,0 +1,10 @@ +type: string +enum: + - CREATED + - PENDING_FUNDING + - PENDING_PROVIDER + - PROCESSING + - COMPLETED + - FAILED + - EXPIRED +description: Stablecoin issuer operation lifecycle status. diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationType.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationType.yaml new file mode 100644 index 000000000..e8f0072ee --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperationType.yaml @@ -0,0 +1,5 @@ +type: string +enum: + - MINT + - BURN +description: Stablecoin issuer operation type. diff --git a/openapi/components/schemas/stablecoins/StablecoinRegisterRequest.yaml b/openapi/components/schemas/stablecoins/StablecoinRegisterRequest.yaml new file mode 100644 index 000000000..fc2b4e62b --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinRegisterRequest.yaml @@ -0,0 +1,20 @@ +type: object +required: + - providerTokenIdentifier + - networkTokenIdentifier + - network +properties: + providerTokenIdentifier: + type: string + description: Provider identifier for the stablecoin. For Brale this maps to `value_type`. + example: ACME + networkTokenIdentifier: + type: string + description: Token identifier in the namespace selected by `network`. + example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k + network: + $ref: StablecoinNetwork.yaml + stablecoinProviderAccountId: + type: string + description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist. + example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index f5b2f8028..f18130315 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -89,8 +89,8 @@ tags: - name: Stablecoins description: >- Stablecoin issuance endpoints. Link provider accounts, register - provider-created stablecoins, create mint/burn quotes, execute them, and - track the resulting operations. + provider-created stablecoins, create direct mint/burn issuer operations, + and track operation status. servers: - url: https://api.lightspark.com/grid/2025-10-13 description: Production server @@ -354,6 +354,18 @@ paths: $ref: paths/sandbox/cards/sandbox_cards_{id}_simulate_credit_authorization_advice.yaml /sandbox/cards/{id}/simulate/return_reversal: $ref: paths/sandbox/cards/sandbox_cards_{id}_simulate_return_reversal.yaml + /stablecoins: + $ref: paths/stablecoins/stablecoins.yaml + /stablecoins/{stablecoinId}: + $ref: paths/stablecoins/stablecoins_{stablecoinId}.yaml + /stablecoins/{stablecoinId}/mints: + $ref: paths/stablecoins/stablecoins_{stablecoinId}_mints.yaml + /stablecoins/{stablecoinId}/burns: + $ref: paths/stablecoins/stablecoins_{stablecoinId}_burns.yaml + /stablecoins/{stablecoinId}/operations: + $ref: paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml + /stablecoin-operations/{stablecoinOperationId}: + $ref: paths/stablecoins/stablecoin-operations_{stablecoinOperationId}.yaml /stablecoin-provider-accounts: $ref: paths/stablecoins/stablecoin-provider-accounts.yaml /stablecoin-provider-accounts/{stablecoinProviderAccountId}: diff --git a/openapi/paths/stablecoins/stablecoin-operations_{stablecoinOperationId}.yaml b/openapi/paths/stablecoins/stablecoin-operations_{stablecoinOperationId}.yaml new file mode 100644 index 000000000..61ca1eaac --- /dev/null +++ b/openapi/paths/stablecoins/stablecoin-operations_{stablecoinOperationId}.yaml @@ -0,0 +1,40 @@ +parameters: + - name: stablecoinOperationId + in: path + description: System-generated stablecoin issuer operation identifier + required: true + schema: + type: string +get: + summary: Get a stablecoin issuer operation + description: Retrieve a single stablecoin issuer operation by its Grid identifier. + operationId: getStablecoinOperation + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinOperation.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Stablecoin operation not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/stablecoins/stablecoins.yaml b/openapi/paths/stablecoins/stablecoins.yaml new file mode 100644 index 000000000..d0b393760 --- /dev/null +++ b/openapi/paths/stablecoins/stablecoins.yaml @@ -0,0 +1,124 @@ +post: + summary: Register an existing provider-created stablecoin + description: | + Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. + operationId: registerStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinRegisterRequest.yaml + responses: + '201': + description: Stablecoin registered + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/Stablecoin.yaml + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Referenced stablecoin provider account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Conflict + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml +get: + summary: List stablecoins + description: Retrieve stablecoins registered to the authenticated platform. + operationId: listStablecoins + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: issuanceStatus + in: query + required: false + schema: + $ref: ../../components/schemas/stablecoins/StablecoinIssuanceStatus.yaml + - name: gridOperationsStatus + in: query + required: false + schema: + $ref: ../../components/schemas/stablecoins/StablecoinGridOperationsStatus.yaml + - name: provider + in: query + required: false + schema: + $ref: ../../components/schemas/stablecoins/StablecoinProvider.yaml + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinListResponse.yaml + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/stablecoins/stablecoins_{stablecoinId}.yaml b/openapi/paths/stablecoins/stablecoins_{stablecoinId}.yaml new file mode 100644 index 000000000..ad950e02c --- /dev/null +++ b/openapi/paths/stablecoins/stablecoins_{stablecoinId}.yaml @@ -0,0 +1,40 @@ +parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string +get: + summary: Get a stablecoin + description: Retrieve a single registered stablecoin by its Grid identifier. + operationId: getStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/Stablecoin.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/stablecoins/stablecoins_{stablecoinId}_burns.yaml b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_burns.yaml new file mode 100644 index 000000000..a42795717 --- /dev/null +++ b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_burns.yaml @@ -0,0 +1,73 @@ +parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string +post: + summary: Create a stablecoin burn operation + description: | + Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinBurn + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this burn request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinBurnRequest.yaml + responses: + '201': + description: Burn operation created + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinOperation.yaml + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Conflict + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error503.yaml diff --git a/openapi/paths/stablecoins/stablecoins_{stablecoinId}_mints.yaml b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_mints.yaml new file mode 100644 index 000000000..cd6dc20eb --- /dev/null +++ b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_mints.yaml @@ -0,0 +1,73 @@ +parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string +post: + summary: Create a stablecoin mint operation + description: | + Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinMint + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this mint request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinMintRequest.yaml + responses: + '201': + description: Mint operation created + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinOperation.yaml + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Conflict + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error503.yaml diff --git a/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml new file mode 100644 index 000000000..f65e9702d --- /dev/null +++ b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml @@ -0,0 +1,56 @@ +parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string +get: + summary: List stablecoin issuer operations + description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. + operationId: listStablecoinOperations + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/stablecoins/StablecoinOperationListResponse.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml From dbba35ead623f095b80936911116bafe61a7d004 Mon Sep 17 00:00:00 2001 From: kphurley7 Date: Wed, 15 Jul 2026 14:02:27 -0500 Subject: [PATCH 2/3] Address stablecoin issuance spec review findings Enforce conditionally-required fields, type the open schemas, add an operations filter, and make operation source naming symmetric. - StablecoinMintFundingSource, StablecoinBurnSource, and StablecoinMintDestination now use the quotes-style oneOf + `type` discriminator with per-variant schemas, so required fields (e.g. externalAccountId for ACH_DEBIT / EXTERNAL_ACCOUNT) are enforced by the schema instead of only being described in prose. StablecoinBurnDestination already unconditionally requires all its fields (single variant), so it is left as-is. - StablecoinFundingInstructions declares its stable fields (rail, network, address, valueType) as typed properties while keeping additionalProperties for rail-specific passthrough, with a comment explaining why it stays open. StablecoinEstimatedDelivery is fully typed (rail, amount, currency, timing). - StablecoinEstimatedDelivery.amount is now expressed in the currency's smallest unit (minor units) to match the rest of the API's money fields. - GET /stablecoins/{stablecoinId}/operations gains an optional operationType query filter. - StablecoinOperation now reports source via a single merged StablecoinOperationSource field (mirroring StablecoinOperationDestination) instead of asymmetric fundingSource/source fields. --- mintlify/openapi.yaml | 349 ++++++++++++++++-- openapi.yaml | 349 ++++++++++++++++-- .../StablecoinAchDebitFundingSource.yaml | 19 + .../stablecoins/StablecoinBurnSource.yaml | 29 +- .../stablecoins/StablecoinBurnSourceBase.yaml | 12 + .../StablecoinEstimatedDelivery.yaml | 20 +- .../StablecoinExternalAccountBurnSource.yaml | 19 + ...blecoinExternalAccountMintDestination.yaml | 19 + .../StablecoinFundingInstructions.yaml | 26 +- .../StablecoinGridInternalBurnSource.yaml | 19 + .../StablecoinGridInternalFundingSource.yaml | 19 + ...StablecoinGridInternalMintDestination.yaml | 19 + .../StablecoinMintDestination.yaml | 26 +- .../StablecoinMintDestinationBase.yaml | 11 + .../StablecoinMintFundingSource.yaml | 43 +-- .../StablecoinMintFundingSourceBase.yaml | 14 + .../stablecoins/StablecoinOperation.yaml | 4 +- .../StablecoinOperationSource.yaml | 40 ++ .../StablecoinProviderBalanceBurnSource.yaml | 14 + ...tablecoinProviderBalanceFundingSource.yaml | 24 ++ ...tablecoinSameDayAchDebitFundingSource.yaml | 19 + .../StablecoinWireFundingSource.yaml | 14 + ...stablecoins_{stablecoinId}_operations.yaml | 6 + 23 files changed, 967 insertions(+), 147 deletions(-) create mode 100644 openapi/components/schemas/stablecoins/StablecoinAchDebitFundingSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinBurnSourceBase.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinExternalAccountBurnSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinGridInternalBurnSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinGridInternalFundingSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinMintFundingSourceBase.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinOperationSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinProviderBalanceBurnSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinProviderBalanceFundingSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinSameDayAchDebitFundingSource.yaml create mode 100644 openapi/components/schemas/stablecoins/StablecoinWireFundingSource.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 9671b1c24..6671a208b 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -7812,6 +7812,12 @@ paths: security: - BasicAuth: [] parameters: + - name: operationType + in: query + description: Filter operations by type. When omitted, both mints and burns are returned. + required: false + schema: + $ref: '#/components/schemas/StablecoinOperationType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -20768,7 +20774,7 @@ components: type: string description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist. example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 - StablecoinMintFundingSource: + StablecoinMintFundingSourceBase: type: object required: - type @@ -20781,24 +20787,124 @@ components: - SAME_DAY_ACH_DEBIT - GRID_INTERNAL_ACCOUNT - PROVIDER_INTERNAL_BALANCE - description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. - externalAccountId: - type: string - description: Grid `ExternalAccount` used as the ACH debit funding account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 - accountId: - type: string - description: Grid internal funding account id for future Grid-funded mint flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 - sourceTokenIdentifier: - type: string - description: Provider token/value type for future provider internal balance funding. - example: USDC - cryptoNetwork: - type: string - description: Source crypto network for future provider internal balance funding. - example: SOLANA - StablecoinMintDestination: + description: Funding source variant. Grid internal account and provider internal balance funding are reserved for follow-up support. + example: ACH_DEBIT + StablecoinWireFundingSource: + title: Wire + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + properties: + type: + type: string + enum: + - WIRE + description: Wire-funded mint. Funding instructions are returned on the resulting operation. + example: WIRE + description: Wire funding source. The issuer wires fiat to the provider using the returned funding instructions. + StablecoinAchDebitFundingSource: + title: ACH Debit + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - ACH_DEBIT + description: ACH debit funding. + example: ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: ACH debit funding source. The provider debits the referenced external account. + StablecoinSameDayAchDebitFundingSource: + title: Same-Day ACH Debit + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - SAME_DAY_ACH_DEBIT + description: Same-day ACH debit funding. + example: SAME_DAY_ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: Same-day ACH debit funding source. The provider debits the referenced external account. + StablecoinGridInternalFundingSource: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account funding. Reserved for the follow-up Grid-funded mint flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal funding account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + description: Grid internal account funding source, reserved for future Grid-funded mint flows. + StablecoinProviderBalanceFundingSource: + title: Provider Internal Balance + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - sourceTokenIdentifier + - cryptoNetwork + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance funding. Reserved for the follow-up provider-balance mint flow. + example: PROVIDER_INTERNAL_BALANCE + sourceTokenIdentifier: + type: string + description: Provider token/value type spent from the provider internal balance. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for the provider internal balance funds. + example: SOLANA + description: Provider internal balance funding source, reserved for future provider-funded mint flows. + StablecoinMintFundingSource: + oneOf: + - $ref: '#/components/schemas/StablecoinWireFundingSource' + - $ref: '#/components/schemas/StablecoinAchDebitFundingSource' + - $ref: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' + - $ref: '#/components/schemas/StablecoinGridInternalFundingSource' + - $ref: '#/components/schemas/StablecoinProviderBalanceFundingSource' + discriminator: + propertyName: type + mapping: + WIRE: '#/components/schemas/StablecoinWireFundingSource' + ACH_DEBIT: '#/components/schemas/StablecoinAchDebitFundingSource' + SAME_DAY_ACH_DEBIT: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalFundingSource' + PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceFundingSource' + StablecoinMintDestinationBase: type: object required: - type @@ -20809,14 +20915,56 @@ components: - EXTERNAL_ACCOUNT - GRID_INTERNAL_ACCOUNT description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - accountId: - type: string - description: Grid internal receiving account id for future Grid-managed mint destinations. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + example: EXTERNAL_ACCOUNT + StablecoinExternalAccountMintDestination: + title: External Account + allOf: + - $ref: '#/components/schemas/StablecoinMintDestinationBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account mint destination. Must reference an active Spark external account. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + description: External account mint destination. + StablecoinGridInternalMintDestination: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinMintDestinationBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal receiving account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. + StablecoinMintDestination: + oneOf: + - $ref: '#/components/schemas/StablecoinExternalAccountMintDestination' + - $ref: '#/components/schemas/StablecoinGridInternalMintDestination' + discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountMintDestination' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalMintDestination' StablecoinMintRequest: type: object required: @@ -20855,30 +21003,60 @@ components: - FAILED - EXPIRED description: Stablecoin issuer operation lifecycle status. - StablecoinBurnSource: + StablecoinOperationSource: type: object + description: 'Source of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint funding source and burn source request variants) so the field is unambiguously deserializable regardless of operation type: `externalAccountId` is present for external-account funded mints and external-account burns, `accountId` for Grid internal account sources, and `sourceTokenIdentifier`/`cryptoNetwork` for provider internal balance sources.' required: - type properties: type: type: string enum: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT - EXTERNAL_ACCOUNT - GRID_INTERNAL_ACCOUNT - PROVIDER_INTERNAL_BALANCE - description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + description: Source variant. Mint funding uses `WIRE`, `ACH_DEBIT`, or `SAME_DAY_ACH_DEBIT`; burns use `EXTERNAL_ACCOUNT`. `GRID_INTERNAL_ACCOUNT` and `PROVIDER_INTERNAL_BALANCE` are shared across mint and burn. + example: ACH_DEBIT externalAccountId: type: string - description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + description: Grid `ExternalAccount` funding the operation, present for ACH debit mints and external-account burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 accountId: type: string - description: Grid internal source account id for future Grid-controlled burn flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + description: Grid internal account id, present for Grid internal account sources. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type, present for provider internal balance sources. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network, present for provider internal balance sources. + example: SOLANA StablecoinFundingInstructions: type: object - description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. + description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. The common fields below are always declared; the exact set of remaining fields is rail- and provider-specific (for example, wire instructions carry bank beneficiary/account/routing details while Spark and on-chain instructions carry a deposit address), so `additionalProperties` stays open to pass those through without a spec change per rail. additionalProperties: true + properties: + rail: + type: string + description: Funding rail these instructions apply to (e.g. `SPARK`, `WIRE`). + example: SPARK + network: + type: string + description: Network the funds should be sent on, when applicable. + example: SPARK + address: + type: string + description: Deposit address the issuer sends funds to, for on-chain or Spark rails. + example: spark1... + valueType: + type: string + description: Provider token/value type the deposit address expects. + example: ACME example: rail: SPARK network: SPARK @@ -20916,9 +21094,27 @@ components: type: object description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. additionalProperties: true + properties: + rail: + type: string + description: Fiat payout rail the estimate applies to. + example: ACH_CREDIT + amount: + type: string + pattern: ^[0-9]+$ + description: Estimated delivery amount in the smallest unit of `currency` (e.g. cents for USD). + example: '100' + currency: + type: string + description: ISO 4217 currency code of the fiat payout. + example: USD + timing: + type: string + description: Human-readable rail timing estimate. + example: 1-3 business days example: rail: ACH_CREDIT - amount: '1.00' + amount: '100' currency: USD timing: 1-3 business days StablecoinOperation: @@ -20959,10 +21155,8 @@ components: type: string description: Stablecoin provider account link used for the operation. example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 - fundingSource: - $ref: '#/components/schemas/StablecoinMintFundingSource' source: - $ref: '#/components/schemas/StablecoinBurnSource' + $ref: '#/components/schemas/StablecoinOperationSource' fundingInstructions: $ref: '#/components/schemas/StablecoinFundingInstructions' destination: @@ -21023,6 +21217,85 @@ components: type: object description: Additional error details additionalProperties: true + StablecoinBurnSourceBase: + type: object + required: + - type + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + example: EXTERNAL_ACCOUNT + StablecoinExternalAccountBurnSource: + title: External Account + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account burn. References an active Spark external account and returns funding instructions. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for the burn. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + description: External account burn source. The issuer funds the provider from a Spark external account. + StablecoinGridInternalBurnSource: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account burn. Reserved for the Grid-controlled ledger movement flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal source account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + description: Grid internal account burn source, reserved for future Grid-controlled burn flows. + StablecoinProviderBalanceBurnSource: + title: Provider Internal Balance + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn. Spends stablecoin funds already held at the provider. + example: PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn source. No external funding is required; the provider spends existing balance. + StablecoinBurnSource: + oneOf: + - $ref: '#/components/schemas/StablecoinExternalAccountBurnSource' + - $ref: '#/components/schemas/StablecoinGridInternalBurnSource' + - $ref: '#/components/schemas/StablecoinProviderBalanceBurnSource' + discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountBurnSource' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalBurnSource' + PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceBurnSource' StablecoinBurnDestination: type: object required: diff --git a/openapi.yaml b/openapi.yaml index 9671b1c24..6671a208b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -7812,6 +7812,12 @@ paths: security: - BasicAuth: [] parameters: + - name: operationType + in: query + description: Filter operations by type. When omitted, both mints and burns are returned. + required: false + schema: + $ref: '#/components/schemas/StablecoinOperationType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -20768,7 +20774,7 @@ components: type: string description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist. example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 - StablecoinMintFundingSource: + StablecoinMintFundingSourceBase: type: object required: - type @@ -20781,24 +20787,124 @@ components: - SAME_DAY_ACH_DEBIT - GRID_INTERNAL_ACCOUNT - PROVIDER_INTERNAL_BALANCE - description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. - externalAccountId: - type: string - description: Grid `ExternalAccount` used as the ACH debit funding account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 - accountId: - type: string - description: Grid internal funding account id for future Grid-funded mint flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 - sourceTokenIdentifier: - type: string - description: Provider token/value type for future provider internal balance funding. - example: USDC - cryptoNetwork: - type: string - description: Source crypto network for future provider internal balance funding. - example: SOLANA - StablecoinMintDestination: + description: Funding source variant. Grid internal account and provider internal balance funding are reserved for follow-up support. + example: ACH_DEBIT + StablecoinWireFundingSource: + title: Wire + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + properties: + type: + type: string + enum: + - WIRE + description: Wire-funded mint. Funding instructions are returned on the resulting operation. + example: WIRE + description: Wire funding source. The issuer wires fiat to the provider using the returned funding instructions. + StablecoinAchDebitFundingSource: + title: ACH Debit + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - ACH_DEBIT + description: ACH debit funding. + example: ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: ACH debit funding source. The provider debits the referenced external account. + StablecoinSameDayAchDebitFundingSource: + title: Same-Day ACH Debit + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - SAME_DAY_ACH_DEBIT + description: Same-day ACH debit funding. + example: SAME_DAY_ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: Same-day ACH debit funding source. The provider debits the referenced external account. + StablecoinGridInternalFundingSource: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account funding. Reserved for the follow-up Grid-funded mint flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal funding account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + description: Grid internal account funding source, reserved for future Grid-funded mint flows. + StablecoinProviderBalanceFundingSource: + title: Provider Internal Balance + allOf: + - $ref: '#/components/schemas/StablecoinMintFundingSourceBase' + - type: object + required: + - type + - sourceTokenIdentifier + - cryptoNetwork + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance funding. Reserved for the follow-up provider-balance mint flow. + example: PROVIDER_INTERNAL_BALANCE + sourceTokenIdentifier: + type: string + description: Provider token/value type spent from the provider internal balance. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for the provider internal balance funds. + example: SOLANA + description: Provider internal balance funding source, reserved for future provider-funded mint flows. + StablecoinMintFundingSource: + oneOf: + - $ref: '#/components/schemas/StablecoinWireFundingSource' + - $ref: '#/components/schemas/StablecoinAchDebitFundingSource' + - $ref: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' + - $ref: '#/components/schemas/StablecoinGridInternalFundingSource' + - $ref: '#/components/schemas/StablecoinProviderBalanceFundingSource' + discriminator: + propertyName: type + mapping: + WIRE: '#/components/schemas/StablecoinWireFundingSource' + ACH_DEBIT: '#/components/schemas/StablecoinAchDebitFundingSource' + SAME_DAY_ACH_DEBIT: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalFundingSource' + PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceFundingSource' + StablecoinMintDestinationBase: type: object required: - type @@ -20809,14 +20915,56 @@ components: - EXTERNAL_ACCOUNT - GRID_INTERNAL_ACCOUNT description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - accountId: - type: string - description: Grid internal receiving account id for future Grid-managed mint destinations. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + example: EXTERNAL_ACCOUNT + StablecoinExternalAccountMintDestination: + title: External Account + allOf: + - $ref: '#/components/schemas/StablecoinMintDestinationBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account mint destination. Must reference an active Spark external account. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + description: External account mint destination. + StablecoinGridInternalMintDestination: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinMintDestinationBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal receiving account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. + StablecoinMintDestination: + oneOf: + - $ref: '#/components/schemas/StablecoinExternalAccountMintDestination' + - $ref: '#/components/schemas/StablecoinGridInternalMintDestination' + discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountMintDestination' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalMintDestination' StablecoinMintRequest: type: object required: @@ -20855,30 +21003,60 @@ components: - FAILED - EXPIRED description: Stablecoin issuer operation lifecycle status. - StablecoinBurnSource: + StablecoinOperationSource: type: object + description: 'Source of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint funding source and burn source request variants) so the field is unambiguously deserializable regardless of operation type: `externalAccountId` is present for external-account funded mints and external-account burns, `accountId` for Grid internal account sources, and `sourceTokenIdentifier`/`cryptoNetwork` for provider internal balance sources.' required: - type properties: type: type: string enum: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT - EXTERNAL_ACCOUNT - GRID_INTERNAL_ACCOUNT - PROVIDER_INTERNAL_BALANCE - description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + description: Source variant. Mint funding uses `WIRE`, `ACH_DEBIT`, or `SAME_DAY_ACH_DEBIT`; burns use `EXTERNAL_ACCOUNT`. `GRID_INTERNAL_ACCOUNT` and `PROVIDER_INTERNAL_BALANCE` are shared across mint and burn. + example: ACH_DEBIT externalAccountId: type: string - description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + description: Grid `ExternalAccount` funding the operation, present for ACH debit mints and external-account burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 accountId: type: string - description: Grid internal source account id for future Grid-controlled burn flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + description: Grid internal account id, present for Grid internal account sources. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type, present for provider internal balance sources. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network, present for provider internal balance sources. + example: SOLANA StablecoinFundingInstructions: type: object - description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. + description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. The common fields below are always declared; the exact set of remaining fields is rail- and provider-specific (for example, wire instructions carry bank beneficiary/account/routing details while Spark and on-chain instructions carry a deposit address), so `additionalProperties` stays open to pass those through without a spec change per rail. additionalProperties: true + properties: + rail: + type: string + description: Funding rail these instructions apply to (e.g. `SPARK`, `WIRE`). + example: SPARK + network: + type: string + description: Network the funds should be sent on, when applicable. + example: SPARK + address: + type: string + description: Deposit address the issuer sends funds to, for on-chain or Spark rails. + example: spark1... + valueType: + type: string + description: Provider token/value type the deposit address expects. + example: ACME example: rail: SPARK network: SPARK @@ -20916,9 +21094,27 @@ components: type: object description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. additionalProperties: true + properties: + rail: + type: string + description: Fiat payout rail the estimate applies to. + example: ACH_CREDIT + amount: + type: string + pattern: ^[0-9]+$ + description: Estimated delivery amount in the smallest unit of `currency` (e.g. cents for USD). + example: '100' + currency: + type: string + description: ISO 4217 currency code of the fiat payout. + example: USD + timing: + type: string + description: Human-readable rail timing estimate. + example: 1-3 business days example: rail: ACH_CREDIT - amount: '1.00' + amount: '100' currency: USD timing: 1-3 business days StablecoinOperation: @@ -20959,10 +21155,8 @@ components: type: string description: Stablecoin provider account link used for the operation. example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 - fundingSource: - $ref: '#/components/schemas/StablecoinMintFundingSource' source: - $ref: '#/components/schemas/StablecoinBurnSource' + $ref: '#/components/schemas/StablecoinOperationSource' fundingInstructions: $ref: '#/components/schemas/StablecoinFundingInstructions' destination: @@ -21023,6 +21217,85 @@ components: type: object description: Additional error details additionalProperties: true + StablecoinBurnSourceBase: + type: object + required: + - type + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + example: EXTERNAL_ACCOUNT + StablecoinExternalAccountBurnSource: + title: External Account + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account burn. References an active Spark external account and returns funding instructions. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for the burn. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + description: External account burn source. The issuer funds the provider from a Spark external account. + StablecoinGridInternalBurnSource: + title: Grid Internal Account + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account burn. Reserved for the Grid-controlled ledger movement flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal source account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + description: Grid internal account burn source, reserved for future Grid-controlled burn flows. + StablecoinProviderBalanceBurnSource: + title: Provider Internal Balance + allOf: + - $ref: '#/components/schemas/StablecoinBurnSourceBase' + - type: object + required: + - type + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn. Spends stablecoin funds already held at the provider. + example: PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn source. No external funding is required; the provider spends existing balance. + StablecoinBurnSource: + oneOf: + - $ref: '#/components/schemas/StablecoinExternalAccountBurnSource' + - $ref: '#/components/schemas/StablecoinGridInternalBurnSource' + - $ref: '#/components/schemas/StablecoinProviderBalanceBurnSource' + discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountBurnSource' + GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalBurnSource' + PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceBurnSource' StablecoinBurnDestination: type: object required: diff --git a/openapi/components/schemas/stablecoins/StablecoinAchDebitFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinAchDebitFundingSource.yaml new file mode 100644 index 000000000..41f143b65 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinAchDebitFundingSource.yaml @@ -0,0 +1,19 @@ +title: ACH Debit +allOf: + - $ref: ./StablecoinMintFundingSourceBase.yaml + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - ACH_DEBIT + description: ACH debit funding. + example: ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: ACH debit funding source. The provider debits the referenced external account. diff --git a/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml b/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml index 13d766e62..be9721452 100644 --- a/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinBurnSource.yaml @@ -1,19 +1,10 @@ -type: object -required: - - type -properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - - PROVIDER_INTERNAL_BALANCE - description: Burn source variant. `EXTERNAL_ACCOUNT` references an active Spark external account and returns funding instructions. `PROVIDER_INTERNAL_BALANCE` spends stablecoin funds already at the provider. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. - externalAccountId: - type: string - description: Grid Spark `ExternalAccount` expected to fund the provider for external-source burns. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 - accountId: - type: string - description: Grid internal source account id for future Grid-controlled burn flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 +oneOf: + - $ref: ./StablecoinExternalAccountBurnSource.yaml + - $ref: ./StablecoinGridInternalBurnSource.yaml + - $ref: ./StablecoinProviderBalanceBurnSource.yaml +discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: ./StablecoinExternalAccountBurnSource.yaml + GRID_INTERNAL_ACCOUNT: ./StablecoinGridInternalBurnSource.yaml + PROVIDER_INTERNAL_BALANCE: ./StablecoinProviderBalanceBurnSource.yaml diff --git a/openapi/components/schemas/stablecoins/StablecoinBurnSourceBase.yaml b/openapi/components/schemas/stablecoins/StablecoinBurnSourceBase.yaml new file mode 100644 index 000000000..cf2123ce1 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinBurnSourceBase.yaml @@ -0,0 +1,12 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Burn source variant. Grid internal account burns are reserved for the Grid-enabled ledger movement flow. + example: EXTERNAL_ACCOUNT diff --git a/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml b/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml index eeb292e71..774cc0e9c 100644 --- a/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinEstimatedDelivery.yaml @@ -1,8 +1,26 @@ type: object description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time. additionalProperties: true +properties: + rail: + type: string + description: Fiat payout rail the estimate applies to. + example: ACH_CREDIT + amount: + type: string + pattern: '^[0-9]+$' + description: Estimated delivery amount in the smallest unit of `currency` (e.g. cents for USD). + example: '100' + currency: + type: string + description: ISO 4217 currency code of the fiat payout. + example: USD + timing: + type: string + description: Human-readable rail timing estimate. + example: 1-3 business days example: rail: ACH_CREDIT - amount: '1.00' + amount: '100' currency: USD timing: 1-3 business days diff --git a/openapi/components/schemas/stablecoins/StablecoinExternalAccountBurnSource.yaml b/openapi/components/schemas/stablecoins/StablecoinExternalAccountBurnSource.yaml new file mode 100644 index 000000000..96fff4de3 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinExternalAccountBurnSource.yaml @@ -0,0 +1,19 @@ +title: External Account +allOf: + - $ref: ./StablecoinBurnSourceBase.yaml + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account burn. References an active Spark external account and returns funding instructions. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid Spark `ExternalAccount` expected to fund the provider for the burn. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000203 + description: External account burn source. The issuer funds the provider from a Spark external account. diff --git a/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml new file mode 100644 index 000000000..1dca3216f --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml @@ -0,0 +1,19 @@ +title: External Account +allOf: + - $ref: ./StablecoinMintDestinationBase.yaml + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + description: External account mint destination. Must reference an active Spark external account. + example: EXTERNAL_ACCOUNT + externalAccountId: + type: string + description: Grid `ExternalAccount` receiving the minted stablecoin. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 + description: External account mint destination. diff --git a/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml b/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml index d9184dc0d..98cf74260 100644 --- a/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinFundingInstructions.yaml @@ -1,6 +1,30 @@ type: object -description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. +description: >- + Provider funding instructions safe to show publicly. Present for wire-funded + mint operations and external-source burn operations when available. The common + fields below are always declared; the exact set of remaining fields is + rail- and provider-specific (for example, wire instructions carry bank + beneficiary/account/routing details while Spark and on-chain instructions + carry a deposit address), so `additionalProperties` stays open to pass those + through without a spec change per rail. additionalProperties: true +properties: + rail: + type: string + description: Funding rail these instructions apply to (e.g. `SPARK`, `WIRE`). + example: SPARK + network: + type: string + description: Network the funds should be sent on, when applicable. + example: SPARK + address: + type: string + description: Deposit address the issuer sends funds to, for on-chain or Spark rails. + example: spark1... + valueType: + type: string + description: Provider token/value type the deposit address expects. + example: ACME example: rail: SPARK network: SPARK diff --git a/openapi/components/schemas/stablecoins/StablecoinGridInternalBurnSource.yaml b/openapi/components/schemas/stablecoins/StablecoinGridInternalBurnSource.yaml new file mode 100644 index 000000000..e98be75c1 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinGridInternalBurnSource.yaml @@ -0,0 +1,19 @@ +title: Grid Internal Account +allOf: + - $ref: ./StablecoinBurnSourceBase.yaml + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account burn. Reserved for the Grid-controlled ledger movement flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal source account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204 + description: Grid internal account burn source, reserved for future Grid-controlled burn flows. diff --git a/openapi/components/schemas/stablecoins/StablecoinGridInternalFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinGridInternalFundingSource.yaml new file mode 100644 index 000000000..aa3d50dbd --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinGridInternalFundingSource.yaml @@ -0,0 +1,19 @@ +title: Grid Internal Account +allOf: + - $ref: ./StablecoinMintFundingSourceBase.yaml + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account funding. Reserved for the follow-up Grid-funded mint flow. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal funding account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + description: Grid internal account funding source, reserved for future Grid-funded mint flows. diff --git a/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml new file mode 100644 index 000000000..9da969cc8 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml @@ -0,0 +1,19 @@ +title: Grid Internal Account +allOf: + - $ref: ./StablecoinMintDestinationBase.yaml + - type: object + required: + - type + - accountId + properties: + type: + type: string + enum: + - GRID_INTERNAL_ACCOUNT + description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. + example: GRID_INTERNAL_ACCOUNT + accountId: + type: string + description: Grid internal receiving account id. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. diff --git a/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml index 60f50a568..debfa29a9 100644 --- a/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml @@ -1,18 +1,8 @@ -type: object -required: - - type -properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - accountId: - type: string - description: Grid internal receiving account id for future Grid-managed mint destinations. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 +oneOf: + - $ref: ./StablecoinExternalAccountMintDestination.yaml + - $ref: ./StablecoinGridInternalMintDestination.yaml +discriminator: + propertyName: type + mapping: + EXTERNAL_ACCOUNT: ./StablecoinExternalAccountMintDestination.yaml + GRID_INTERNAL_ACCOUNT: ./StablecoinGridInternalMintDestination.yaml diff --git a/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml b/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml new file mode 100644 index 000000000..bf0257071 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml @@ -0,0 +1,11 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. + example: EXTERNAL_ACCOUNT diff --git a/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml index ce0d30684..0b6dc1ec6 100644 --- a/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinMintFundingSource.yaml @@ -1,29 +1,14 @@ -type: object -required: - - type -properties: - type: - type: string - enum: - - WIRE - - ACH_DEBIT - - SAME_DAY_ACH_DEBIT - - GRID_INTERNAL_ACCOUNT - - PROVIDER_INTERNAL_BALANCE - description: Funding source variant. For `ACH_DEBIT` and `SAME_DAY_ACH_DEBIT`, `externalAccountId` is required. Grid internal account and provider internal balance funding are reserved for follow-up support. - externalAccountId: - type: string - description: Grid `ExternalAccount` used as the ACH debit funding account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 - accountId: - type: string - description: Grid internal funding account id for future Grid-funded mint flows. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 - sourceTokenIdentifier: - type: string - description: Provider token/value type for future provider internal balance funding. - example: USDC - cryptoNetwork: - type: string - description: Source crypto network for future provider internal balance funding. - example: SOLANA +oneOf: + - $ref: ./StablecoinWireFundingSource.yaml + - $ref: ./StablecoinAchDebitFundingSource.yaml + - $ref: ./StablecoinSameDayAchDebitFundingSource.yaml + - $ref: ./StablecoinGridInternalFundingSource.yaml + - $ref: ./StablecoinProviderBalanceFundingSource.yaml +discriminator: + propertyName: type + mapping: + WIRE: ./StablecoinWireFundingSource.yaml + ACH_DEBIT: ./StablecoinAchDebitFundingSource.yaml + SAME_DAY_ACH_DEBIT: ./StablecoinSameDayAchDebitFundingSource.yaml + GRID_INTERNAL_ACCOUNT: ./StablecoinGridInternalFundingSource.yaml + PROVIDER_INTERNAL_BALANCE: ./StablecoinProviderBalanceFundingSource.yaml diff --git a/openapi/components/schemas/stablecoins/StablecoinMintFundingSourceBase.yaml b/openapi/components/schemas/stablecoins/StablecoinMintFundingSourceBase.yaml new file mode 100644 index 000000000..09c2ca0e4 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinMintFundingSourceBase.yaml @@ -0,0 +1,14 @@ +type: object +required: + - type +properties: + type: + type: string + enum: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Funding source variant. Grid internal account and provider internal balance funding are reserved for follow-up support. + example: ACH_DEBIT diff --git a/openapi/components/schemas/stablecoins/StablecoinOperation.yaml b/openapi/components/schemas/stablecoins/StablecoinOperation.yaml index 1a0c28f5f..068ed5594 100644 --- a/openapi/components/schemas/stablecoins/StablecoinOperation.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinOperation.yaml @@ -35,10 +35,8 @@ properties: type: string description: Stablecoin provider account link used for the operation. example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001 - fundingSource: - $ref: StablecoinMintFundingSource.yaml source: - $ref: StablecoinBurnSource.yaml + $ref: StablecoinOperationSource.yaml fundingInstructions: $ref: StablecoinFundingInstructions.yaml destination: diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationSource.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationSource.yaml new file mode 100644 index 000000000..3d2edb9a9 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinOperationSource.yaml @@ -0,0 +1,40 @@ +type: object +description: >- + Source of a stablecoin operation, as reported on a + `StablecoinOperation`. A single merged shape (superset of the + mint funding source and burn source request variants) so the field + is unambiguously deserializable regardless of operation type: + `externalAccountId` is present for external-account funded mints and + external-account burns, `accountId` for Grid internal account + sources, and `sourceTokenIdentifier`/`cryptoNetwork` for provider + internal balance sources. +required: + - type +properties: + type: + type: string + enum: + - WIRE + - ACH_DEBIT + - SAME_DAY_ACH_DEBIT + - EXTERNAL_ACCOUNT + - GRID_INTERNAL_ACCOUNT + - PROVIDER_INTERNAL_BALANCE + description: Source variant. Mint funding uses `WIRE`, `ACH_DEBIT`, or `SAME_DAY_ACH_DEBIT`; burns use `EXTERNAL_ACCOUNT`. `GRID_INTERNAL_ACCOUNT` and `PROVIDER_INTERNAL_BALANCE` are shared across mint and burn. + example: ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` funding the operation, present for ACH debit mints and external-account burns. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + accountId: + type: string + description: Grid internal account id, present for Grid internal account sources. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102 + sourceTokenIdentifier: + type: string + description: Provider token/value type, present for provider internal balance sources. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network, present for provider internal balance sources. + example: SOLANA diff --git a/openapi/components/schemas/stablecoins/StablecoinProviderBalanceBurnSource.yaml b/openapi/components/schemas/stablecoins/StablecoinProviderBalanceBurnSource.yaml new file mode 100644 index 000000000..f3e7cd1a3 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinProviderBalanceBurnSource.yaml @@ -0,0 +1,14 @@ +title: Provider Internal Balance +allOf: + - $ref: ./StablecoinBurnSourceBase.yaml + - type: object + required: + - type + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn. Spends stablecoin funds already held at the provider. + example: PROVIDER_INTERNAL_BALANCE + description: Provider internal balance burn source. No external funding is required; the provider spends existing balance. diff --git a/openapi/components/schemas/stablecoins/StablecoinProviderBalanceFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinProviderBalanceFundingSource.yaml new file mode 100644 index 000000000..458608c27 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinProviderBalanceFundingSource.yaml @@ -0,0 +1,24 @@ +title: Provider Internal Balance +allOf: + - $ref: ./StablecoinMintFundingSourceBase.yaml + - type: object + required: + - type + - sourceTokenIdentifier + - cryptoNetwork + properties: + type: + type: string + enum: + - PROVIDER_INTERNAL_BALANCE + description: Provider internal balance funding. Reserved for the follow-up provider-balance mint flow. + example: PROVIDER_INTERNAL_BALANCE + sourceTokenIdentifier: + type: string + description: Provider token/value type spent from the provider internal balance. + example: USDC + cryptoNetwork: + type: string + description: Source crypto network for the provider internal balance funds. + example: SOLANA + description: Provider internal balance funding source, reserved for future provider-funded mint flows. diff --git a/openapi/components/schemas/stablecoins/StablecoinSameDayAchDebitFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinSameDayAchDebitFundingSource.yaml new file mode 100644 index 000000000..2a3e1c470 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinSameDayAchDebitFundingSource.yaml @@ -0,0 +1,19 @@ +title: Same-Day ACH Debit +allOf: + - $ref: ./StablecoinMintFundingSourceBase.yaml + - type: object + required: + - type + - externalAccountId + properties: + type: + type: string + enum: + - SAME_DAY_ACH_DEBIT + description: Same-day ACH debit funding. + example: SAME_DAY_ACH_DEBIT + externalAccountId: + type: string + description: Grid `ExternalAccount` debited to fund the mint. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101 + description: Same-day ACH debit funding source. The provider debits the referenced external account. diff --git a/openapi/components/schemas/stablecoins/StablecoinWireFundingSource.yaml b/openapi/components/schemas/stablecoins/StablecoinWireFundingSource.yaml new file mode 100644 index 000000000..752f33788 --- /dev/null +++ b/openapi/components/schemas/stablecoins/StablecoinWireFundingSource.yaml @@ -0,0 +1,14 @@ +title: Wire +allOf: + - $ref: ./StablecoinMintFundingSourceBase.yaml + - type: object + required: + - type + properties: + type: + type: string + enum: + - WIRE + description: Wire-funded mint. Funding instructions are returned on the resulting operation. + example: WIRE + description: Wire funding source. The issuer wires fiat to the provider using the returned funding instructions. diff --git a/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml index f65e9702d..18dd93307 100644 --- a/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml +++ b/openapi/paths/stablecoins/stablecoins_{stablecoinId}_operations.yaml @@ -14,6 +14,12 @@ get: security: - BasicAuth: [] parameters: + - name: operationType + in: query + description: Filter operations by type. When omitted, both mints and burns are returned. + required: false + schema: + $ref: ../../components/schemas/stablecoins/StablecoinOperationType.yaml - name: limit in: query description: Maximum number of results to return (default 20, max 100) From 4a952c5ce5a89ebe66bbdd66787a85061bef1865 Mon Sep 17 00:00:00 2001 From: kphurley7 Date: Thu, 16 Jul 2026 08:57:48 -0500 Subject: [PATCH 3/3] Unify stablecoin mint/operation destinations to a single accountId Address shreyav review on the stablecoin issuance spec: represent internal and external accounts as one `account` reference instead of an internal/external discriminator, matching the existing AccountDestination / AccountQuoteSource / AccountTransactionDestination pattern where the id prefix (`ExternalAccount:` / `InternalAccount:`) disambiguates. - StablecoinMintDestination: collapse the oneOf + type=EXTERNAL_ACCOUNT|GRID_INTERNAL_ACCOUNT discriminator (and its StablecoinExternalAccountMintDestination / StablecoinGridInternalMintDestination / StablecoinMintDestinationBase variants) into a single object with one required `accountId`. Also removes the conditionally-required-fields concern since a single always-required field has no conditional case. - StablecoinOperationDestination: merge externalAccountId/accountId into a single `accountId` and drop the now-redundant external/internal `type` (the id prefix carries it). Keeps the flat, non-oneOf merged shape; `rail` still distinguishes burn (fiat payout) from mint destinations. Regenerated openapi.yaml and mintlify/openapi.yaml via redocly bundle. --- mintlify/openapi.yaml | 14208 ++++++++++------ openapi.yaml | 14208 ++++++++++------ ...blecoinExternalAccountMintDestination.yaml | 19 - ...StablecoinGridInternalMintDestination.yaml | 19 - .../StablecoinMintDestination.yaml | 26 +- .../StablecoinMintDestinationBase.yaml | 11 - .../StablecoinOperationDestination.yaml | 30 +- 7 files changed, 17310 insertions(+), 11211 deletions(-) delete mode 100644 openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml delete mode 100644 openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml delete mode 100644 openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 6671a208b..a6069a95d 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -23,6 +23,8 @@ tags: description: Customer management endpoints for creating and updating customer information - name: Contact Verification description: Endpoints for verifying a customer's email and phone via one-time codes. Required only for customers whose payment provider mandates contact verification (e.g. EU customers); other providers return 409. + - name: Strong Customer Authentication + description: Endpoints for authorizing money-movement operations that require Strong Customer Authentication. Relevant only for customers in a region where SCA is required (e.g. EU); customers outside SCA-regulated regions never see an SCA challenge and these endpoints return 409. - name: KYC/KYB Verifications description: Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. - name: Documents @@ -125,6 +127,18 @@ paths: sendFromEmailSenderName: Acme Notifications replyToEmailAddress: support@acme.com logoUrl: https://acme.com/logo.png + cardTokenization2faConfig: + displayName: Acme + logoUrl: https://acme.com/card-email-logo.png + email: + fromAddress: cards@acme.com + fromName: Acme Cards + replyToAddress: support@acme.com + subject: Your Acme card verification code + bodyText: Use this code to finish adding your Acme card to your digital wallet. + sms: + templateSid: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bodyText: Use this code to finish adding your Acme card to your digital wallet. responses: '200': description: Configuration updated successfully @@ -412,9 +426,9 @@ paths: address: line1: 123 Market Street line2: Suite 400 - city: San Francisco - state: CA - postalCode: '94105' + city: Seattle + state: WA + postalCode: '98101' country: US responses: '201': @@ -674,9 +688,9 @@ paths: - USDC address: line1: 456 Market St - city: San Francisco - state: CA - postalCode: '94103' + city: Seattle + state: WA + postalCode: '98101' country: US businessUpdate: summary: Update business customer example @@ -691,9 +705,9 @@ paths: taxId: EIN-123456789 address: line1: 100 Technology Parkway - city: Palo Alto - state: CA - postalCode: '94304' + city: Bellevue + state: WA + postalCode: '98004' country: US embeddedWalletEmailUpdate: summary: Embedded Wallet email update request (both steps) @@ -1125,180 +1139,97 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/internal-accounts: + /sca/factors: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer whose factors are listed or enrolled. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 get: - summary: List Customer internal accounts + summary: List enrolled SCA factors description: | - Retrieve a list of internal accounts with optional filtering parameters. Returns all - internal accounts that match the specified filters. If no filters are provided, returns all internal accounts - (paginated). + List the Strong Customer Authentication factors the customer has enrolled. - Internal accounts are created automatically when a customer is created based on the platform configuration. - operationId: listCustomerInternalAccounts + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: listScaFactors tags: - - Internal Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by internal accounts associated with a specific customer - required: false - schema: - type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string responses: '200': - description: Successful operation + description: The customer's enrolled SCA factors. content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ScaFactorList' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /platform/internal-accounts: - get: - summary: List platform internal accounts - description: | - Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - - These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. - operationId: listPlatformInternalAccounts - tags: - - Internal Accounts - security: - - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/PlatformInternalAccountListResponse' - '400': - description: Bad request - Invalid parameters + '404': + description: Customer not found content: application/json: schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. content: application/json: schema: - $ref: '#/components/schemas/Error401' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts: - get: - summary: List Customer external accounts + post: + summary: Start SCA factor enrollment description: | - Retrieve a list of external accounts with optional filtering parameters. Returns all - external accounts that match the specified filters. If no filters are provided, returns all external accounts - (paginated). + Begin enrolling an SCA factor for the customer. Enrollment covers the + explicit, opt-in factors a customer chooses to add — the request body's + `type` selects `TOTP` or `PASSKEY`. Returns the factor-specific material + needed to finish via `POST /sca/factors/confirm`. - External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. - operationId: listCustomerExternalAccounts + `SMS_OTP` is implicit and is not enrolled through this endpoint. Every + customer in an SCA-regulated region has a verified phone number from + customer creation (via the Contact Verification flows — + `POST /customers/{customerId}/verify-phone` and `.../verify-phone/confirm`), + so SMS is always available as a factor with no extra setup and appears + among the customer's enrolled factors in `GET /sca/factors`. + + A customer may have **only one passkey**. Starting a passkey enrollment when + one is already enrolled returns `409` (`PASSKEY_ALREADY_ENROLLED`) — delete it + via `DELETE /sca/factors/{credentialId}` first. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startScaFactorEnrollment tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by external accounts associated with a specific customer - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaFactorEnrollRequestOneOf' responses: '200': - description: Successful operation + description: Enrollment started; the factor-specific completion material is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/ScaFactorEnrollStartOneOf' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1309,18 +1240,52 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer (`CONFLICT`), or a passkey enrollment was requested while one is already enrolled (`PASSKEY_ALREADY_ENROLLED`) — only one passkey per customer is supported. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /sca/factors/confirm: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer confirming a factor enrollment. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Add a new external account - description: Register a new external bank account for a customer. - operationId: createCustomerExternalAccount + summary: Confirm SCA factor enrollment + description: | + Finalize the factor enrollment started by `POST /sca/factors`. The request + body is discriminated by `type`: for `TOTP`, submit the shared `secret` from + the start call plus the first `code`; for `PASSKEY`, submit the WebAuthn + `credential` the device produced plus the `origin` it was produced against. + The threaded secret/credential binds the confirmation to its enrollment, so + no separate id is needed. + + A TOTP confirmation returns one-time recovery codes (shown once); a passkey + confirmation returns the enrolled factor. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the TOTP code is always `123456`. + operationId: confirmScaFactorEnrollment tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -1328,48 +1293,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + $ref: '#/components/schemas/ScaFactorConfirmRequestOneOf' responses: - '201': - description: External account created successfully + '200': + description: Factor enrolled; the factor-specific result is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/ScaFactorConfirmResponseOneOf' '400': - description: Bad request + description: Invalid or incorrect confirmation proof content: application/json: schema: @@ -1380,8 +1313,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: SCA is not required for this customer. content: application/json: schema: @@ -1392,29 +1331,36 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}: + /sca/factors/{credentialId}: parameters: - - name: externalAccountId + - name: customerId + in: query + description: The unique identifier of the customer whose factor is being deleted. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: credentialId in: path - description: System-generated unique external account identifier + description: The credential id of the enrolled factor to delete (from the factor's `credentialId`). required: true schema: type: string - get: - summary: Get customer external account by ID - description: Retrieve a customer external account by its system-generated ID - operationId: getCustomerExternalAccountById + delete: + summary: Delete an enrolled SCA factor + description: | + Delete an enrolled SCA factor by its credential id. Today only `PASSKEY` + factors carry a `credentialId` and are deletable this way. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: deleteScaFactor tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' + '204': + description: Factor deleted; no content is returned. '401': description: Unauthorized content: @@ -1422,28 +1368,68 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or factor not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete customer external account by ID - description: Delete a customer external account by its system-generated ID - operationId: deleteCustomerExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] + /sca/login/start: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer starting an SCA login. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Start an SCA login + description: | + Begin an SCA login for the customer with the chosen factor, opening the + end-user SCA session (an exemption gating read / account access beyond the + per-transaction window). Returns factor-specific material: `SMS_OTP` + dispatches a code and returns a `challengeId` + `expiresAt`; `TOTP` returns + only the factor (the customer reads the code from their app); `PASSKEY` + returns WebAuthn `passkeyOptions`. Complete with + `POST /sca/login/complete`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startScaLogin + tags: + - Strong Customer Authentication + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginStartRequest' responses: - '204': - description: External account deleted successfully + '200': + description: SCA login started; factor-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginStart' + '400': + description: Invalid or unknown factor + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1451,60 +1437,63 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts: - get: - summary: List platform external accounts + /sca/login/complete: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer completing an SCA login. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Complete an SCA login description: | - Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. + Finalize an SCA login by submitting the proof for the started factor + (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for + `PASSKEY`), echoing the `challengeId` for `SMS_OTP`. Returns the + reported session status. - These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. - operationId: listPlatformExternalAccounts + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: completeScaLogin tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginCompleteRequest' responses: '200': - description: Successful operation + description: SCA login completed; the session status is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/ScaLoginComplete' '400': - description: Bad request - Invalid parameters + description: Invalid or expired proof content: application/json: schema: @@ -1515,18 +1504,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '423': + description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /sca/record-event: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer the security event is recorded for. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Add a new platform external account - description: Register a new external bank account for the platform. - operationId: createPlatformExternalAccount + summary: Record a security event + description: | + Record a client-side security-relevant event for the customer with Grid's risk engine (e.g. a sign-in, a sensitive view), to + feed adaptive-authentication signals. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: recordSecurityEvent tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -1534,46 +1554,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + $ref: '#/components/schemas/RecordSecurityEventRequest' responses: - '201': - description: External account created successfully + '200': + description: Event recorded; the customer's resulting login-security state is returned (including any lockout). content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/RecordSecurityEventResponse' '400': - description: Bad request + description: Invalid event type content: application/json: schema: @@ -1584,41 +1574,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: SCA is not required for this customer. content: application/json: schema: $ref: '#/components/schemas/Error409' + '423': + description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}: + /sca/factors/reset: parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier + - name: customerId + in: query + description: The unique identifier of the customer resetting a factor. required: true schema: type: string - get: - summary: Get platform external account by ID - description: Retrieve a platform external account by its system-generated ID - operationId: getPlatformExternalAccountById + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Start a 2FA reset + description: | + Begin recovering a lost enrolled factor via a liveness-gated, poll-based + flow. Opens the liveness check and returns a `resetId` plus the + opaque liveness handles (`livenessAccessToken` / `verificationLink`) the end + user completes it with. Poll + `GET /sca/factors/reset/{resetId}` until liveness + passes, then call the complete endpoint. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startTwoFactorReset tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResetStartRequest' responses: - '200': - description: Successful operation + '201': + description: Reset initiated; the reset handle and liveness material are returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/TwoFactorResetStart' + '400': + description: Invalid or unknown factor + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1626,28 +1649,62 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '429': + description: Too many reset attempts. Reset initiation is rate-limited to 5 per 24 hours per customer; retry after the window indicated by `Retry-After`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete platform external account by ID - description: Delete a platform external account by its system-generated ID - operationId: deletePlatformExternalAccountById + /sca/factors/reset/{resetId}: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer whose reset status is polled. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: resetId + in: path + description: The reset handle returned by the start call. + required: true + schema: + type: string + get: + summary: Get 2FA reset status + description: | + Poll the status of an in-progress 2FA reset until it reaches the liveness-passed value, after which the reset can be completed. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: getTwoFactorResetStatus tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] responses: - '204': - description: External account deleted successfully + '200': + description: The current reset status. + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResetStatus' '401': description: Unauthorized content: @@ -1655,42 +1712,64 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or reset not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners: + /sca/factors/reset/{resetId}/complete: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer completing the reset. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: resetId + in: path + description: The reset handle returned by the start call. + required: true + schema: + type: string post: - summary: Create a beneficial owner + summary: Complete a 2FA reset description: | - Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. - operationId: createBeneficialOwner + Complete a 2FA reset once liveness has passed, clearing the lost factor so + the customer can re-enroll. + + For an `SMS_OTP` reset, supply the new `mobile` number in the body — completing + the reset enrolls it as the customer's number. Other factors need no body. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: completeTwoFactorReset tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerCreateRequest' + $ref: '#/components/schemas/TwoFactorResetCompleteRequest' responses: - '201': - description: Beneficial owner created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwner' + '204': + description: Reset completed; no content is returned. '400': - description: Bad request - Invalid parameters + description: Reset not ready (liveness not yet passed) content: application/json: schema: @@ -1702,57 +1781,54 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Customer or reset not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List beneficial owners - description: | - Retrieve a list of beneficial owners for a business customer. - operationId: listBeneficialOwners - tags: - - KYC/KYB Verifications + /customers/external-accounts/{externalAccountId}/trust: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being trusted. + required: true + schema: + type: string + post: + summary: Start trusting a beneficiary + description: | + Begin trusting (whitelisting) an external account so future sends to it can + skip the per-transaction SCA ceremony. Returns the `scaChallenge` to satisfy + (when one is issued). Complete with + `POST /customers/external-accounts/{externalAccountId}/trust/confirm`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startBeneficiaryTrust + tags: + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: customerId - in: query - description: The business customer ID - required: true - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string responses: '200': - description: Successful operation + description: Beneficiary trust started; the SCA challenge (if any) is returned. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerListResponse' + $ref: '#/components/schemas/BeneficiaryTrustStart' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1763,35 +1839,67 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners/{beneficialOwnerId}: - get: - summary: Get a beneficial owner - description: Retrieve details of a specific beneficial owner by ID. - operationId: getBeneficialOwner + /customers/external-accounts/{externalAccountId}/trust/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being trusted. + required: true + schema: + type: string + post: + summary: Confirm trusting a beneficiary + description: | + Finalize trusting a beneficiary (identified by the `externalAccountId` in the + path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or + `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when + one was issued. Returns `trusted: true`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: confirmBeneficiaryTrust tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: beneficialOwnerId - in: path - description: Beneficial owner ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' responses: '200': - description: Successful operation + description: Beneficiary trusted. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/BeneficiaryTrustConfirm' + '400': + description: Invalid or expired proof + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1799,47 +1907,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update a beneficial owner - description: Update details of a specific beneficial owner. Only provided fields are updated. - operationId: updateBeneficialOwner + /customers/external-accounts/{externalAccountId}/untrust: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being untrusted. + required: true + schema: + type: string + post: + summary: Start untrusting a beneficiary + description: | + Begin untrusting (removing the trusted mark from) an external account, so + future sends to it are dynamically linked and require the per-transaction SCA + ceremony again. Returns the `scaChallenge` to satisfy (when one is issued). + Complete with + `POST /customers/external-accounts/{externalAccountId}/untrust/confirm`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startBeneficiaryUntrust tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: beneficialOwnerId - in: path - description: Beneficial owner ID - required: true - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficial owner updated successfully + description: Beneficiary untrust started; the SCA challenge (if any) is returned. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/BeneficiaryTrustStart' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1851,40 +1967,62 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /documents: + /customers/external-accounts/{externalAccountId}/untrust/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being untrusted. + required: true + schema: + type: string post: - summary: Upload a document + summary: Confirm untrusting a beneficiary description: | - Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. + Finalize untrusting a beneficiary (identified by the `externalAccountId` in + the path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or + `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when + one was issued. Returns `trusted: false`. - Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. - operationId: uploadDocument + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: confirmBeneficiaryUntrust tags: - - Documents + - Strong Customer Authentication security: - BasicAuth: [] requestBody: - $ref: '#/components/requestBodies/DocumentUploadRequestBody' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' responses: - '201': - description: Document uploaded successfully + '200': + description: Beneficiary untrusted. content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/BeneficiaryTrustConfirm' '400': - description: Bad request - Invalid file type, size, or parameters + description: Invalid or expired proof content: application/json: schema: @@ -1896,33 +2034,56 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document holder not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /customers/internal-accounts: get: - summary: List documents + summary: List Customer internal accounts description: | - Retrieve a list of documents with optional filtering by document holder. - operationId: listDocuments + Retrieve a list of internal accounts with optional filtering parameters. Returns all + internal accounts that match the specified filters. If no filters are provided, returns all internal accounts + (paginated). + + Internal accounts are created automatically when a customer is created based on the platform configuration. + operationId: listCustomerInternalAccounts tags: - - Documents + - Internal Accounts security: - BasicAuth: [] parameters: - - name: documentHolder + - name: currency in: query - description: Filter by document holder ID (Customer or BeneficialOwner) + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by internal accounts associated with a specific customer required: false schema: type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -1944,7 +2105,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DocumentListResponse' + $ref: '#/components/schemas/InternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -1963,74 +2124,107 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents/{documentId}: + /platform/internal-accounts: get: - summary: Get a document by ID - description: Retrieve details and metadata of a specific document by ID. - operationId: getDocument + summary: List platform internal accounts + description: | + Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. + + These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. + operationId: listPlatformInternalAccounts tags: - - Documents + - Internal Accounts security: - BasicAuth: [] parameters: - - name: documentId - in: path - description: Document ID - required: true + - name: currency + in: query + description: Filter by currency code + required: false schema: type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Document' - '401': - description: Unauthorized + $ref: '#/components/schemas/PlatformInternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Document not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - put: - summary: Replace a document + /customers/external-accounts: + get: + summary: List Customer external accounts description: | - Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. - operationId: replaceDocument + Retrieve a list of external accounts with optional filtering parameters. Returns all + external accounts that match the specified filters. If no filters are provided, returns all external accounts + (paginated). + + External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. + operationId: listCustomerExternalAccounts tags: - - Documents + - External Accounts security: - BasicAuth: [] parameters: - - name: documentId - in: path - description: Document ID - required: true + - name: currency + in: query + description: Filter by currency code + required: false schema: type: string - requestBody: - $ref: '#/components/requestBodies/DocumentReplaceRequestBody' - responses: - '200': - description: Document replaced successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Document' + - name: customerId + in: query + description: Filter by external accounts associated with a specific customer + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountListResponse' '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2041,51 +2235,79 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Document not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete a document - description: | - Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. - operationId: deleteDocument + post: + summary: Add a new external account + description: Register a new external bank account for a customer. + operationId: createCustomerExternalAccount tags: - - Documents + - External Accounts security: - BasicAuth: [] - parameters: - - name: documentId - in: path - description: Document ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '204': - description: Document deleted successfully - '401': - description: Unauthorized + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Document not found + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '409': - description: Conflict - Document cannot be deleted (already submitted for verification) + description: Conflict - External account already exists content: application/json: schema: @@ -2096,110 +2318,60 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications: - post: - summary: Submit customer for verification - description: | - Trigger KYC (individual) or KYB (business) verification for a customer. - The response indicates whether all required information has been provided. - If data is missing, the `errors` array describes exactly what needs to be - supplied before verification can proceed. - - Call this endpoint again after resolving errors to re-submit. - - ### What to collect for KYB - - Before submitting a `BUSINESS` customer, collect the following via - `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: - - **Business identifying information** - - Entity full legal name - - Doing Business As (DBA) name, if applicable - - Physical address — principal place of business - - Countries of operation - - Identification number — U.S. taxpayer identification number, or, for a - foreign business without one, alternative government-issued documentation - certifying the existence of the business - - **Ownership and control structure** — collected for **one control person** - (an individual with significant responsibility to control, manage, or - direct the legal entity) **and all beneficial owners** (every individual - who owns 25% or more, directly or indirectly). For each, provide: - - Full name - - Date of birth - - Address - - Identification number: - - U.S. persons — SSN or ITIN - - Non-U.S. persons — one or more of: ITIN, passport (with country of - issuance), alien identification card, or another government-issued - photo ID evidencing nationality or residence - - **Required documents** - - Company formation and existence documents (certificate of incorporation, - articles of association, etc.) - - Proof of ownership and control structure (organization and ownership - chart, shareholder agreements, operating agreements, register of members, - or certification of controlling person and beneficial owners) - - Proof of address dated within the last 3 months (utility bill, bank - statement, lease agreement, or official correspondence) - - Tax ID or equivalent identifying-number documents - - For non-U.S. beneficial owners — passport plus one additional - government-issued ID - operationId: createVerification + /customers/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string + get: + summary: Get customer external account by ID + description: Retrieve a customer external account by its system-generated ID + operationId: getCustomerExternalAccountById tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: | - Verification status returned. Check `verificationStatus` and `errors` to determine next steps. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Verification' - examples: - missingInfo: - summary: Verification blocked by missing data - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000001 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_FIELD - field: customer.address.line1 - reason: Business address line 1 is required - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 - type: MISSING_FIELD - field: personalInfo.birthDate - reason: Date of birth is required for beneficial owners - createdAt: '2025-10-03T12:00:00Z' - submitted: - summary: Verification submitted successfully - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: IN_PROGRESS - errors: [] - createdAt: '2025-10-03T12:00:00Z' - '400': - description: Bad request - Invalid parameters + $ref: '#/components/schemas/ExternalAccount' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Delete customer external account by ID + description: |- + Delete a customer external account by its system-generated ID. + An account that is currently a trusted beneficiary for SCA cannot be deleted — untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete. + operationId: deleteCustomerExternalAccountById + tags: + - External Accounts + security: + - BasicAuth: [] + responses: + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -2207,39 +2379,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Returned with `BENEFICIARY_TRUSTED` when the external account is currently a trusted beneficiary. Untrust it first, then delete. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /platform/external-accounts: get: - summary: List verifications + summary: List platform external accounts description: | - Retrieve a list of verifications with optional filtering by customer ID and status. - operationId: listVerifications + Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. + + These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. + operationId: listPlatformExternalAccounts tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] parameters: - - name: customerId + - name: currency in: query - description: Filter by customer ID + description: Filter by currency code required: false schema: type: string - - name: verificationStatus - in: query - description: Filter by verification status - required: false - schema: - $ref: '#/components/schemas/VerificationStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2261,7 +2436,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/VerificationListResponse' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -2280,94 +2455,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications/{verificationId}: - get: - summary: Get a verification - description: Retrieve details of a specific verification by ID. - operationId: getVerification + post: + summary: Add a new platform external account + description: Register a new external bank account for the platform. + operationId: createPlatformExternalAccount tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] - parameters: - - name: verificationId - in: path - description: Verification ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Successful operation + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Verification' + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Verification not found + '409': + description: Conflict - External account already exists content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-in: - post: - summary: Create a transfer-in request - description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. - operationId: createTransferIn + /platform/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string + get: + summary: Get platform external account by ID + description: Retrieve a platform external account by its system-generated ID + operationId: getPlatformExternalAccountById tags: - - Same-Currency Transfers + - External Accounts security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 responses: - '201': - description: Transfer-in request created successfully + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '400': - description: Bad request - Invalid parameters + $ref: '#/components/schemas/ExternalAccount' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Delete platform external account by ID + description: Delete a platform external account by its system-generated ID + operationId: deletePlatformExternalAccountById + tags: + - External Accounts + security: + - BasicAuth: [] + responses: + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -2375,7 +2589,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or account not found + description: External account not found content: application/json: schema: @@ -2386,49 +2600,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-out: + /beneficial-owners: post: - summary: Create a transfer-out request + summary: Create a beneficial owner description: | - Transfer funds from an internal account to an external account for a specific customer. - operationId: createTransferOut + Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. + operationId: createBeneficialOwner tags: - - Same-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - paymentRail: ACH - amount: 12550 - remittanceInformation: '12345' + $ref: '#/components/schemas/BeneficialOwnerCreateRequest' responses: '201': - description: Transfer-out request created successfully + description: Beneficial owner created successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/BeneficialOwner' '400': description: Bad request - Invalid parameters content: @@ -2442,7 +2636,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or account not found + description: Customer not found content: application/json: schema: @@ -2453,45 +2647,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/uma/{receiverUmaAddress}: get: - summary: Look up an UMA address for payment + summary: List beneficial owners description: | - Lookup a receiving UMA address to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given UMA address. - operationId: lookupUma + Retrieve a list of beneficial owners for a business customer. + operationId: listBeneficialOwners tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: receiverUmaAddress - in: path - description: UMA address of the intended recipient + - name: customerId + in: query + description: The business customer ID required: true schema: type: string - - name: senderUmaAddress + - name: limit in: query - description: UMA address of the sender (optional if customerId is provided) + description: Maximum number of results to return (default 20, max 100) required: false schema: - type: string - - name: customerId + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor in: query - description: System ID of the sender (optional if senderUmaAddress is provided) + description: Cursor for pagination (returned from previous request) required: false schema: type: string responses: '200': - description: Successful lookup + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ReceiverUmaLookupResponse' + $ref: '#/components/schemas/BeneficialOwnerListResponse' '400': - description: Bad request - Missing or invalid parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2502,74 +2697,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/external-account/{accountId}: + /beneficial-owners/{beneficialOwnerId}: get: - summary: Look up an external account for payment - description: | - Lookup an external account by ID to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. - operationId: lookupExternalAccount + summary: Get a beneficial owner + description: Retrieve details of a specific beneficial owner by ID. + operationId: getBeneficialOwner tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: accountId + - name: beneficialOwnerId in: path - description: System-generated ID of the external account + description: Beneficial owner ID required: true schema: type: string - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string responses: '200': - description: Successful lookup - content: - application/json: - schema: - $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' - '400': - description: Bad request - Missing or invalid parameters + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BeneficialOwner' '401': description: Unauthorized content: @@ -2577,55 +2733,51 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}: - get: - summary: Get quote by ID - description: | - Retrieve a quote by its ID. If the quote has been settled, it will include - the transaction ID. This allows clients to track the full lifecycle of a payment - from quote creation to settlement. - operationId: getQuoteById + patch: + summary: Update a beneficial owner + description: Update details of a specific beneficial owner. Only provided fields are updated. + operationId: updateBeneficialOwner tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: quoteId + - name: beneficialOwnerId in: path - description: ID of the quote to retrieve + description: Beneficial owner ID required: true schema: type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Quote retrieved successfully + description: Beneficial owner updated successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/BeneficialOwner' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2633,7 +2785,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Beneficial owner not found content: application/json: schema: @@ -2644,125 +2796,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes: + /documents: post: - summary: Create a transfer quote + summary: Upload a document description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. - - **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. - - **Account to UMA**: Transfer from an internal account to an UMA address. - - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - - **Key Features:** - - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount - - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. - operationId: createQuote + Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. + operationId: uploadDocument tags: - - Cross-Currency Transfers + - Documents security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/QuoteRequest' - examples: - accountToAccount: - summary: Account to Account Transfer - value: - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - lockedCurrencySide: SENDING - lockedCurrencyAmount: 10000 - description: Transfer between accounts, either internal or external. - accountToUma: - summary: Account to UMA Address Transfer - value: - lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: UMA_ADDRESS - umaAddress: $receiver@uma.domain.com - currency: EUR - lockedCurrencySide: SENDING - lockedCurrencyAmount: 1000 - description: 'Payment for invoice #1234' - realTimeFundingToSparkWallet: - summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. - value: - source: - sourceType: REALTIME_FUNDING - customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 - currency: USD - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 - lockedCurrencySide: RECEIVING - lockedCurrencyAmount: 10000 - immediatelyExecute: true - description: Bitcoin reward payout! + $ref: '#/components/requestBodies/DocumentUploadRequestBody' responses: '201': - description: | - Transfer quote created successfully. The response includes exchange rates, - fees, and transfer details. For transfers involving UMA addresses, payment - instructions are also included for execution through banking systems. + description: Document uploaded successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' - example: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - createdAt: '2025-10-03T12:00:00Z' - expiresAt: '2025-10-03T12:05:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: EUR - name: Euro - symbol: € - decimals: 2 - totalSendingAmount: 10000 - totalReceivingAmount: 9200 - exchangeRate: 0.92 - feesIncluded: 10 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 + $ref: '#/components/schemas/Document' '400': - description: Bad request - Missing or invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2773,195 +2829,34 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Document holder not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /quotes/{quoteId}/execute: - post: - summary: Execute a quote + get: + summary: List documents description: | - Execute a quote by its ID. This endpoint initiates the transfer between - the source and destination accounts. - - This endpoint can only be used for quotes with a `source` which is either an internal account, - or has direct pull functionality (e.g. ACH pull with an external account). - - When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, - the request must include a `Grid-Wallet-Signature` header. The header value - is the full Grid wallet signature built over the `payloadToSign` value from - the quote's `paymentInstructions[].accountOrWalletInfo` entry with the - session private key of a verified authentication credential on the source - Embedded Wallet. - - Once executed, the quote cannot be cancelled and the transfer will be processed. - operationId: executeQuote - tags: - - Cross-Currency Transfers - security: - - BasicAuth: [] - parameters: - - name: quoteId - in: path - required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - responses: - '200': - description: | - Quote confirmed successfully. The transfer has been initiated and - the quote status has been updated. - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - '400': - description: Bad request - Invalid quote ID or quote cannot be confirmed - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Quote already confirmed, expired, or in invalid state - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /transactions: - get: - summary: List transactions - description: | - Retrieve a paginated list of transactions with optional filtering. - The transactions can be filtered by customer ID, platform customer ID, UMA address, - date range, status, and transaction type. - - Card transactions are included and identified by `type: CARD`. In Sandbox this is how - you discover a `CardTransaction` id after simulating an authorization — list the - transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` - to the clearing and return simulate endpoints. - operationId: listTransactions + Retrieve a list of documents with optional filtering by document holder. + operationId: listDocuments tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: customerId - in: query - description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. - required: false - schema: - type: string - - name: platformCustomerId - in: query - description: Filter by platform-specific customer ID - required: false - schema: - type: string - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate + - name: documentHolder in: query - description: Filter by end date (inclusive) in ISO 8601 format + description: Filter by document holder ID (Customer or BeneficialOwner) required: false schema: type: string - format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2977,23 +2872,13 @@ paths: required: false schema: type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/DocumentListResponse' '400': description: Bad request - Invalid parameters content: @@ -3012,29 +2897,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string + /documents/{documentId}: get: - summary: Get transaction by ID - description: Retrieve detailed information about a specific transaction. - operationId: getTransactionById + summary: Get a document by ID + description: Retrieve details and metadata of a specific document by ID. + operationId: getDocument tags: - - Transactions + - Documents security: - BasicAuth: [] + parameters: + - name: documentId + in: path + description: Document ID + required: true + schema: + type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Document' '401': description: Unauthorized content: @@ -3042,7 +2927,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: @@ -3053,38 +2938,33 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/confirm: - post: - summary: Confirm receipt delivery + put: + summary: Replace a document description: | - Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. - operationId: confirmReceiptDelivery + Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. + operationId: replaceDocument tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: transactionId + - name: documentId in: path - description: Unique identifier of the transaction to confirm receipt delivery for + description: Document ID required: true schema: type: string requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' + $ref: '#/components/requestBodies/DocumentReplaceRequestBody' responses: '200': - description: Receipt delivery confirmation recorded successfully + description: Document replaced successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Document' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -3096,7 +2976,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: @@ -3107,43 +2987,25 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/approve: - post: - summary: Approve a pending incoming payment + delete: + summary: Delete a document description: | - Approve a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously approve payments after async processing. - operationId: approvePendingPayment + Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. + operationId: deleteDocument tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: transactionId + - name: documentId in: path - description: Unique identifier of the transaction to approve + description: Document ID required: true schema: type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ApprovePaymentRequest' responses: - '200': - description: Payment approved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - Invalid parameters or payment cannot be approved - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Document deleted successfully '401': description: Unauthorized content: @@ -3151,13 +3013,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. + description: Conflict - Document cannot be deleted (already submitted for verification) content: application/json: schema: @@ -3168,39 +3030,106 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/reject: + /verifications: post: - summary: Reject a pending incoming payment + summary: Submit customer for verification description: | - Reject a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously reject payments after additional processing. - operationId: rejectPendingPayment + Trigger KYC (individual) or KYB (business) verification for a customer. + The response indicates whether all required information has been provided. + If data is missing, the `errors` array describes exactly what needs to be + supplied before verification can proceed. + + Call this endpoint again after resolving errors to re-submit. + + ### What to collect for KYB + + Before submitting a `BUSINESS` customer, collect the following via + `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: + + **Business identifying information** + - Entity full legal name + - Doing Business As (DBA) name, if applicable + - Physical address — principal place of business + - Countries of operation + - Identification number — U.S. taxpayer identification number, or, for a + foreign business without one, alternative government-issued documentation + certifying the existence of the business + + **Ownership and control structure** — collected for **one control person** + (an individual with significant responsibility to control, manage, or + direct the legal entity) **and all beneficial owners** (every individual + who owns 25% or more, directly or indirectly). For each, provide: + - Full name + - Date of birth + - Address + - Identification number: + - U.S. persons — SSN or ITIN + - Non-U.S. persons — one or more of: ITIN, passport (with country of + issuance), alien identification card, or another government-issued + photo ID evidencing nationality or residence + + **Required documents** + - Company formation and existence documents (certificate of incorporation, + articles of association, etc.) + - Proof of ownership and control structure (organization and ownership + chart, shareholder agreements, operating agreements, register of members, + or certification of controlling person and beneficial owners) + - Proof of address dated within the last 3 months (utility bill, bank + statement, lease agreement, or official correspondence) + - Tax ID or equivalent identifying-number documents + - For non-U.S. beneficial owners — passport plus one additional + government-issued ID + operationId: createVerification tags: - - Transactions + - KYC/KYB Verifications security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to reject - required: true - schema: - type: string requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/RejectPaymentRequest' + $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: Payment rejected successfully + description: | + Verification status returned. Check `verificationStatus` and `errors` to determine next steps. content: application/json: schema: - $ref: '#/components/schemas/IncomingTransaction' + $ref: '#/components/schemas/Verification' + examples: + missingInfo: + summary: Verification blocked by missing data + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000001 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_FIELD + field: customer.address.line1 + reason: Business address line 1 is required + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 + type: MISSING_FIELD + field: personalInfo.birthDate + reason: Date of birth is required for beneficial owners + createdAt: '2025-10-03T12:00:00Z' + submitted: + summary: Verification submitted successfully + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: IN_PROGRESS + errors: [] + createdAt: '2025-10-03T12:00:00Z' '400': - description: Bad request - Invalid parameters or payment cannot be rejected + description: Bad request - Invalid parameters content: application/json: schema: @@ -3212,55 +3141,61 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /crypto/estimate-withdrawal-fee: - post: - summary: Estimate crypto withdrawal fee + get: + summary: List verifications description: | - Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. - operationId: estimateCryptoWithdrawalFee + Retrieve a list of verifications with optional filtering by customer ID and status. + operationId: listVerifications tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' - examples: - estimateUSDCWithdrawal: - summary: Estimate fee for USDC withdrawal on Solana - value: - internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - currency: USDC - cryptoNetwork: SOLANA - amount: 1000000 - destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU + parameters: + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: verificationStatus + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/VerificationStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: Fee estimation returned successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' + $ref: '#/components/schemas/VerificationListResponse' '400': description: Bad request - Invalid parameters content: @@ -3273,8 +3208,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /verifications/{verificationId}: + get: + summary: Get a verification + description: Retrieve details of a specific verification by ID. + operationId: getVerification + tags: + - KYC/KYB Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Verification ID + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Verification' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Verification not found content: application/json: schema: @@ -3285,24 +3255,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/webhooks/test: + /transfer-in: post: - summary: Send a test webhook - description: Send a test webhook to the configured endpoint - operationId: sendTestWebhook + summary: Create a transfer-in request + description: | + Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + operationId: createTransferIn tags: - - Sandbox + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '200': - description: Webhook delivered successfully + '201': + description: Transfer-in request created successfully content: application/json: schema: - $ref: '#/components/schemas/TestWebhookResponse' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request - Webhook delivery failed + description: Bad request - Invalid parameters content: application/json: schema: @@ -3313,133 +3308,129 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/csv: + /transfer-out: post: - summary: Upload customers via CSV file + summary: Create a transfer-out request description: | - Upload a CSV file containing customer information for bulk creation. The CSV file should follow - a specific format with required and optional columns based on customer type. - - ### CSV Format - The CSV file should have the following columns: - - Required columns for all customers: - - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) - - platformCustomerId: Your platform's unique identifier for the customer - - customerType: Either "INDIVIDUAL" or "BUSINESS" - - Required columns for individual customers: - - fullName: Individual's full name - - birthDate: Date of birth in YYYY-MM-DD format - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Required columns for business customers: - - businessLegalName: Legal name of the business - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Optional columns for all customers: - - addressLine2: Street address line 2 - - platformAccountId: Your platform's identifier for the bank account - - description: Optional description for the customer - - Optional columns for individual customers: - - email: Customer's email address - - Optional columns for business customers: - - businessRegistrationNumber: Business registration number - - businessTaxId: Tax identification number - - ### Example CSV - ```csv - umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName - john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US - acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US - ``` - - The upload process is asynchronous and will return a job ID that can be used to track progress. - You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. - operationId: uploadCustomersCsv + Transfer funds from an internal account to an external account for a specific customer. + operationId: createTransferOut tags: - - Customers + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: - multipart/form-data: + application/json: schema: - type: object - required: - - file - properties: - file: - type: string - format: binary - description: CSV file containing customer information + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + amount: 12550 + remittanceInformation: '12345' responses: - '202': - description: CSV upload accepted for processing + '201': + description: | + Transfer-out request created successfully. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJobAccepted' + $ref: '#/components/schemas/TransactionOneOf' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/jobs/{jobId}: + /receiver/uma/{receiverUmaAddress}: get: - summary: Get bulk import job status + summary: Look up an UMA address for payment description: | - Retrieve the current status and results of a bulk customer import job. This endpoint can be used - to track the progress of both CSV uploads. - - The response includes: - - Overall job status - - Progress statistics - - Detailed error information for failed entries - - Completion timestamp when finished - operationId: getBulkCustomerImportJob + Lookup a receiving UMA address to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given UMA address. + operationId: lookupUma tags: - - Customers + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: jobId + - name: receiverUmaAddress in: path - description: ID of the bulk import job to retrieve + description: UMA address of the intended recipient required: true schema: type: string + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: '200': - description: Job status retrieved successfully + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJob' + $ref: '#/components/schemas/ReceiverUmaLookupResponse' + '400': + description: Bad request - Missing or invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3447,42 +3438,69 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Job not found + description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). content: application/json: schema: $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations: - post: - summary: Create an UMA invitation + /receiver/external-account/{accountId}: + get: + summary: Look up an external account for payment description: | - Create an UMA invitation from a given platform customer. - operationId: createInvitation + Lookup an external account by ID to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. + operationId: lookupExternalAccount tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UmaInvitationCreateRequest' + parameters: + - name: accountId + in: path + description: System-generated ID of the external account + required: true + schema: + type: string + example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: - '201': - description: Invitation created successfully + '200': + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -3493,36 +3511,56 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}: + /quotes/{quoteId}: get: - summary: Get an UMA invitation by code + summary: Get quote by ID description: | - Retrieve details about an UMA invitation by its invitation code. - operationId: getInvitation + Retrieve a quote by its ID. If the quote has been settled, it will include + the transaction ID. This allows clients to track the full lifecycle of a payment + from quote creation to settlement. + operationId: getQuoteById tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode + - name: quoteId in: path - description: The code of the invitation to get + description: ID of the quote to retrieve required: true schema: type: string responses: '200': - description: Invitation retrieved successfully + description: Quote retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' '401': description: Unauthorized content: @@ -3530,7 +3568,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Invitation not found + description: Quote not found content: application/json: schema: @@ -3541,45 +3579,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/claim: + /quotes: post: - summary: Claim an UMA invitation + summary: Create a transfer quote description: | - Claim an UMA invitation by associating it with an invitee UMA address. + Generate a quote for a cross-currency transfer between any combination of accounts + and UMA addresses. This endpoint handles currency exchange and provides the necessary + instructions to execute the transfer. - When an invitation is successfully claimed: - 1. The invitation status changes from PENDING to CLAIMED - 2. The invitee UMA address is associated with the invitation - 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation + **Transfer Types Supported:** + - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to UMA**: Transfer from an internal account to an UMA address. + - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - This endpoint allows customers to accept invitations sent to them by other UMA customers. - operationId: claimInvitation + **Key Features:** + - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount + - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates + - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + + **Important:** If you are transferring funds in the same currency (no exchange required), + use the `/transfer-in` or `/transfer-out` endpoints instead. + operationId: createQuote tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode - in: path - description: The code of the invitation to claim - required: true + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationClaimRequest' + $ref: '#/components/schemas/QuoteRequest' + examples: + accountToAccount: + summary: Account to Account Transfer + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + lockedCurrencySide: SENDING + lockedCurrencyAmount: 10000 + description: Transfer between accounts, either internal or external. + accountToUma: + summary: Account to UMA Address Transfer + value: + lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: UMA_ADDRESS + umaAddress: $receiver@uma.domain.com + currency: EUR + lockedCurrencySide: SENDING + lockedCurrencyAmount: 1000 + description: 'Payment for invoice #1234' + realTimeFundingToSparkWallet: + summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. + value: + source: + sourceType: REALTIME_FUNDING + customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 + currency: USD + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 + lockedCurrencySide: RECEIVING + lockedCurrencyAmount: 10000 + immediatelyExecute: true + description: Bitcoin reward payout! responses: - '200': - description: Invitation claimed successfully + '201': + description: | + Transfer quote created successfully. The response includes exchange rates, + fees, and transfer details. For transfers involving UMA addresses, payment + instructions are also included for execution through banking systems. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' + example: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + createdAt: '2025-10-03T12:00:00Z' + expiresAt: '2025-10-03T12:05:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: EUR + name: Euro + symbol: € + decimals: 2 + totalSendingAmount: 10000 + totalReceivingAmount: 9200 + exchangeRate: 0.92 + feesIncluded: 10 + platformFeesIncluded: 0 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 + '202': + description: | + Quote created but awaiting Strong Customer Authentication. Returned only for customers whose provider requires SCA (e.g. EU) on a realtime-funding source: the quote has status `PENDING_AUTHORIZATION` and carries an `scaChallenge`, and `paymentInstructions` are **withheld** until the challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once authorized, the quote's `paymentInstructions` are populated. + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -3590,89 +3716,155 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Invitation not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/cancel: + '501': + description: Not implemented + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /quotes/{quoteId}/execute: post: - summary: Cancel an UMA invitation + summary: Execute a quote description: | - Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. + Execute a quote by its ID. This endpoint initiates the transfer between + the source and destination accounts. - When an invitation is cancelled: - 1. The invitation status changes from PENDING to CANCELLED - 2. The invitation can no longer be claimed - 3. The invitation URL will show as cancelled when accessed + This endpoint can only be used for quotes with a `source` which is either an internal account, + or has direct pull functionality (e.g. ACH pull with an external account). - Only pending invitations can be cancelled. Attempting to cancel an invitation - that is already claimed, expired, or cancelled will result in an error. - operationId: cancelInvitation + When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, + the request must include a `Grid-Wallet-Signature` header. The header value + is the full Grid wallet signature built over the `payloadToSign` value from + the quote's `paymentInstructions[].accountOrWalletInfo` entry with the + session private key of a verified authentication credential on the source + Embedded Wallet. + + Once executed, the quote cannot be cancelled and the transfer will be processed. + operationId: executeQuote tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode + - name: quoteId in: path - description: The code of the invitation to cancel required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteQuoteRequest' responses: '200': - description: Invitation cancelled successfully + description: | + Quote processed. The outcome depends on whether SCA applies to the customer (currently EU customers): + + - **No SCA required:** the transfer is initiated and the quote status advances (`PROCESSING` / `COMPLETED`). + + - **SCA required:** the transfer is **not** initiated yet. The quote is returned with status `PENDING_AUTHORIZATION` and an `scaChallenge`; release the transfer by authorizing the quote you already hold — `POST /quotes/{quoteId}/authorize` (re-calling `execute` returns 409). A pre-funded send is a single challenge, so one authorization releases it. The challenge (and its SMS code / passkey assertion) only comes into existence once this call initiates the transfer, so the proof is always supplied on the follow-up authorize, never inline on this call. If an SMS code lapses, re-send it via `POST /quotes/{quoteId}/authorize/resend`. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' '400': - description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) + description: Bad request - Invalid quote ID or quote cannot be confirmed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Only the platform which created the invitation can cancel it + '404': + description: Quote not found content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Invitation not found + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Quote already confirmed, expired, or in invalid state content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/send: + /quotes/{quoteId}/authorize: + parameters: + - name: quoteId + in: path + description: The unique identifier of the quote whose SCA challenge is being authorized. + required: true + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000006 post: - summary: Simulate sending funds + summary: Authorize a quote's SCA challenge description: | - Simulate sending funds to the bank account as instructed in the quote. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxSend + Satisfy the Strong Customer Authentication challenge carried by a quote in + `PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof. + + This is used for realtime-funding quotes: the quote is returned with an + `scaChallenge` and **without** `paymentInstructions`; once authorized, the + quote advances and its `paymentInstructions` are populated so the customer + can fund the transfer. + + As with all SCA, a quote may require more than one authorization: after + authorizing, if the quote is still `PENDING_AUTHORIZATION` it carries the + next `scaChallenge` — authorize that too, repeating until it advances (see + `ScaChallenge`). + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS code is always `123456`. + operationId: authorizeQuote tags: - - Sandbox + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -3680,16 +3872,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SandboxSendRequest' + $ref: '#/components/schemas/ScaAuthorization' responses: '200': - description: Funds received successfully + description: Challenge authorized; the updated quote is returned. content: application/json: schema: - $ref: '#/components/schemas/OutgoingTransaction' + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Invalid or expired authorization proof content: application/json: schema: @@ -3700,178 +3892,176 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer, or the quote has no pending challenge to authorize. Also returned with `SCA_SESSION_REQUIRED` when the customer's SCA login session is missing or expired — re-authenticate the customer, then authorize again. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '423': + description: Returned with `ACCOUNT_LOCKED` when the customer's login is temporarily locked after too many failed authorization attempts. The customer must wait for the lock to expire before authorizing again. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when authorization attempts for this challenge happen too frequently (for example, repeated bad codes brute-forcing the OTP). The challenge may be invalidated after too many failed attempts. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/uma/receive: + /quotes/{quoteId}/authorize/resend: + parameters: + - name: quoteId + in: path + description: The unique identifier of the quote whose SCA challenge code should be re-sent. + required: true + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000006 post: - summary: Simulate payment send to test receiving an UMA payment + summary: Resend a quote's SCA challenge code description: | - Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxReceive - tags: - - Sandbox + Re-send the one-time code for a realtime-funding quote in + `PENDING_AUTHORIZATION` status whose `scaChallenge.factor` is `SMS_OTP`. The + existing challenge is reused — no new challenge is issued, and its + `scaChallenge.expiresAt` is **not** extended; once the challenge is past + `expiresAt` it can no longer be authorized. + + Only meaningful for customers in a region where SCA is required (e.g. EU); + a 409 is returned otherwise. `PASSKEY` challenges cannot be re-sent and return 409. + + In sandbox, the code is always `123456`. + operationId: resendQuoteScaCode + tags: + - Strong Customer Authentication security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '200': - description: Payment triggered successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Code re-sent. '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Sender or receiver not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/internal-accounts/{accountId}/fund: - post: - summary: Simulate funding an internal account - description: | - Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxFundInternalAccount - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: accountId - in: path - required: true - description: The ID of the internal account to fund - schema: - type: string - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxFundRequest' - examples: - fundUSDAccount: - summary: Fund USD internal account with $1,000 - value: - amount: 100000 - fundBTCAccount: - summary: Fund BTC internal account with 0.01 BTC - value: - amount: 1000000 - responses: - '200': - description: Internal account funded successfully - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token + '409': + description: SCA is not required for this customer, the quote has no pending SMS challenge, or the challenge uses a factor whose code cannot be re-sent (e.g. passkey). content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Internal account not found + $ref: '#/components/schemas/Error409' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when codes are re-sent too frequently, to avoid spamming the customer's phone. Clients should back off and retry after the interval indicated by the `Retry-After` response header. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /uma-providers: + /transactions: get: - summary: List available Counterparty Providers + summary: List transactions description: | - Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. - operationId: getAvailableUmaProviders + Retrieve a paginated list of transactions with optional filtering. + The transactions can be filtered by customer ID, platform customer ID, UMA address, + date range, status, and transaction type. + + Card transactions are included and identified by `type: CARD`. In Sandbox this is how + you discover a `CardTransaction` id after simulating an authorization — list the + transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` + to the clearing and return simulate endpoints. + operationId: listTransactions tags: - - Available UMA Providers + - Transactions + security: + - BasicAuth: [] parameters: - - in: query - name: countryCode - description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. + - name: customerId + in: query + description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. required: false schema: type: string - example: US - - in: query - name: currencyCode - description: The ISO 4217 currency code to filter providers by supported currency. + - name: platformCustomerId + in: query + description: Filter by platform-specific customer ID required: false schema: type: string - example: USD - - in: query - name: hasBlockedProviders - description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) required: false schema: - type: boolean + type: string + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -3897,15 +4087,19 @@ paths: - asc - desc default: desc - security: - - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/UmaProviderListResponse' + $ref: '#/components/schemas/TransactionListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3918,113 +4112,77 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens: - post: - summary: Create a new API token - description: Create a new API token to access the Grid APIs. - operationId: createToken + /transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get transaction by ID + description: Retrieve detailed information about a specific transaction. + operationId: getTransactionById tags: - - API Tokens + - Transactions security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '201': - description: API token created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ApiToken' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List tokens + /transactions/{transactionId}/confirm: + post: + summary: Confirm receipt delivery description: | - Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match - the specified filters. If no filters are provided, returns all tokens (paginated). - operationId: listTokens + Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. + operationId: confirmReceiptDelivery tags: - - API Tokens + - Transactions security: - BasicAuth: [] parameters: - - name: name - in: query - description: Filter by name of the token - required: false - schema: - type: string - - name: createdAfter - in: query - description: Filter customers created after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: createdBefore - in: query - description: Filter customers created before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedAfter - in: query - description: Filter customers updated after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedBefore - in: query - description: Filter customers updated before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false + - name: transactionId + in: path + description: Unique identifier of the transaction to confirm receipt delivery for + required: true schema: type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Successful operation + description: Receipt delivery confirmation recorded successfully content: application/json: schema: - $ref: '#/components/schemas/TokenListResponse' + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request - Invalid parameters content: @@ -4037,35 +4195,55 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens/{tokenId}: - parameters: - - name: tokenId - in: path - description: System-generated unique token identifier - required: true - schema: - type: string - get: - summary: Get API token by ID - description: Retrieve an API token by their system-generated ID - operationId: getTokenById + /transactions/{transactionId}/approve: + post: + summary: Approve a pending incoming payment + description: | + Approve a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously approve payments after async processing. + operationId: approvePendingPayment tags: - - API Tokens + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to approve + required: true + schema: + type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovePaymentRequest' responses: '200': - description: Successful operation + description: Payment approved successfully content: application/json: schema: - $ref: '#/components/schemas/ApiToken' + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request - Invalid parameters or payment cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -4073,30 +4251,56 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Token not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete API token by ID - description: Delete an API token by their system-generated ID - operationId: deleteTokenById + /transactions/{transactionId}/reject: + post: + summary: Reject a pending incoming payment + description: | + Reject a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously reject payments after additional processing. + operationId: rejectPendingPayment tags: - - API Tokens + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to reject + required: true + schema: + type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/RejectPaymentRequest' responses: - '204': - description: API token deleted successfully + '200': + description: Payment rejected successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' '400': - description: Bad request + description: Bad request - Invalid parameters or payment cannot be rejected content: application/json: schema: @@ -4108,208 +4312,124 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Token not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}: - parameters: - - name: id - in: path - description: The id of the internal account to update. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - patch: - summary: Update internal account + /transactions/{transactionId}/cancel: + post: + summary: Cancel a bank transfer description: | - Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - - Updating wallet privacy is a two-step signed-retry flow: - - 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. - operationId: updateInternalAccount + Request cancellation of a pending bank transfer — an ACH transfer (push or pull) or a wire — before it has settled, for example a payment or collection initiated outside of the receiving bank's processing window. + Whether a transfer can still be cancelled is determined by the banking partner that is settling it: the request is forwarded to the partner's own cancellation facility, and a transfer that the partner has already processed (or that is otherwise past its cancellation window) cannot be cancelled. Cancellation applies to bank-rail transfers; requests for transaction types that cannot be cancelled are rejected. + operationId: cancelTransaction tags: - - Internal Accounts + - Transactions security: - BasicAuth: [] parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + - name: transactionId + in: path + description: Unique identifier of the transaction to cancel + required: true schema: type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/InternalAccountUpdateRequest' - examples: - updateWalletPrivacy: - summary: Update wallet privacy request (both steps) - value: - privateEnabled: true + $ref: '#/components/schemas/CancelTransactionRequest' responses: '200': - description: Signed retry accepted. Returns the updated internal account. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - examples: - enabled: - summary: Wallet privacy enabled - value: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: EMBEDDED_WALLET - status: ACTIVE - balance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - privateEnabled: true - createdAt: '2026-04-08T15:30:00Z' - updatedAt: '2026-04-08T15:35:02Z' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Cancellation requested successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Internal account update challenge - value: - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request + description: Bad request - Invalid parameters or the transaction cannot be cancelled content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - The transaction has already settled or is otherwise past the point where it can be cancelled. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}/export: + /crypto/estimate-withdrawal-fee: post: - summary: Export internal account wallet credentials + summary: Estimate crypto withdrawal fee description: | - Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. - - Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): - - 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. - - The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. - operationId: exportInternalAccount + Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. + operationId: estimateCryptoWithdrawalFee tags: - - Internal Accounts + - Cross-Currency Transfers security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the internal account to export. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountExportRequest' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' examples: - export: - summary: Export request (both steps) + estimateUSDCWithdrawal: + summary: Estimate fee for USDC withdrawal on Solana value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + currency: USDC + cryptoNetwork: SOLANA + amount: 1000000 + destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU responses: '200': - description: Signed retry accepted. Returns the encrypted wallet credentials. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountExportResponse' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Fee estimation returned successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: @@ -4326,237 +4446,118 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials: + /sandbox/webhooks/test: post: - summary: Create an authentication credential - description: | - Register an authentication credential for an Embedded Wallet customer. - - Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. - - Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. - operationId: createAuthCredential + summary: Send a test webhook + description: Send a test webhook to the configured endpoint + operationId: sendTestWebhook tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' - examples: - emailOtp: - summary: Add an email OTP credential - value: - type: EMAIL_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - smsOtp: - summary: Add an SMS OTP credential - value: - type: SMS_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oauth: - summary: Add an OAuth credential - value: - type: OAUTH - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - passkey: - summary: Add a passkey credential - value: - type: PASSKEY - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: iPhone Face-ID - challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx - attestation: - credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY - clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 - attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow - transports: - - internal - - hybrid responses: - '201': - description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthMethodResponse' - examples: - emailOtp: - summary: Email OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - smsOtp: - summary: SMS OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: SMS_OTP - nickname: '+14155550123' - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - oauth: - summary: OAuth credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - passkey: - summary: Passkey credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - '202': - description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + '200': + description: Webhook delivered successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Additional email OTP credential challenge - value: - type: EMAIL_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - smsOtp: - summary: Additional SMS OTP credential challenge - value: - type: SMS_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - oauth: - summary: Additional OAuth credential challenge - value: - type: OAUTH - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - passkey: - summary: Additional passkey credential challenge - value: - type: PASSKEY - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/TestWebhookResponse' '400': - description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. + description: Bad request - Webhook delivery failed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Internal account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List authentication credentials - description: |- - Retrieve all authentication credentials registered on an Embedded Wallet internal account. + /customers/bulk/csv: + post: + summary: Upload customers via CSV file + description: | + Upload a CSV file containing customer information for bulk creation. The CSV file should follow + a specific format with required and optional columns based on customer type. - The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. - operationId: listAuthCredentials - tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: accountId - in: query - description: Internal account id whose authentication credentials to list. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - responses: - '200': - description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialListResponse' - examples: - multipleCredentials: - summary: Internal account with multiple authentication credentials - value: - data: - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:35:00Z' - updatedAt: '2026-04-08T15:35:00Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-09T10:15:00Z' - updatedAt: '2026-04-09T10:15:00Z' - empty: - summary: No credentials registered on the account - value: - data: [] - '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + ### CSV Format + The CSV file should have the following columns: + + Required columns for all customers: + - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) + - platformCustomerId: Your platform's unique identifier for the customer + - customerType: Either "INDIVIDUAL" or "BUSINESS" + + Required columns for individual customers: + - fullName: Individual's full name + - birthDate: Date of birth in YYYY-MM-DD format + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Required columns for business customers: + - businessLegalName: Legal name of the business + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Optional columns for all customers: + - addressLine2: Street address line 2 + - platformAccountId: Your platform's identifier for the bank account + - description: Optional description for the customer + + Optional columns for individual customers: + - email: Customer's email address + + Optional columns for business customers: + - businessRegistrationNumber: Business registration number + - businessTaxId: Tax identification number + + ### Example CSV + ```csv + umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName + john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US + acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US + ``` + + The upload process is asynchronous and will return a job ID that can be used to track progress. + You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. + operationId: uploadCustomersCsv + tags: + - Customers + security: + - BasicAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - file + properties: + file: + type: string + format: binary + description: CSV file containing customer information + responses: + '202': + description: CSV upload accepted for processing content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJobAccepted' '401': description: Unauthorized content: @@ -4569,68 +4570,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}: - delete: - summary: Revoke an authentication credential + /customers/bulk/jobs/{jobId}: + get: + summary: Get bulk import job status description: | - Revoke an authentication credential on an Embedded Wallet internal account. - - Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: - - 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + Retrieve the current status and results of a bulk customer import job. This endpoint can be used + to track the progress of both CSV uploads. - The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. - operationId: revokeAuthCredential + The response includes: + - Overall job status + - Progress statistics + - Detailed error information for failed entries + - Completion timestamp when finished + operationId: getBulkCustomerImportJob tags: - - Embedded Wallet Auth + - Customers security: - BasicAuth: [] parameters: - - name: id + - name: jobId in: path - description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + description: ID of the bulk import job to retrieve required: true schema: type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Authentication credential revoked successfully. - '400': - description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + '200': + description: Job status retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJob' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Job not found content: application/json: schema: @@ -4641,103 +4619,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/verify: + /invitations: post: - summary: Verify an authentication credential + summary: Create an UMA invitation description: | - Complete the verification step for a previously created authentication credential and issue a session. - - For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. - In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. - - For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. - - On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. - operationId: verifyAuthCredential + Create an UMA invitation from a given platform customer. + operationId: createInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' - examples: - emailOtp: - summary: Verify an email OTP credential (first leg) - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - smsOtp: - summary: Verify an SMS OTP credential (first leg) - value: - type: SMS_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - emailOtpSignedRetry: - summary: Signed retry completing an email OTP verification - description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - oauth: - summary: Verify an OAuth credential - value: - type: OAUTH - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - passkey: - summary: Verify a passkey credential - value: - type: PASSKEY - assertion: - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 - authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA - signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 + $ref: '#/components/schemas/UmaInvitationCreateRequest' responses: - '200': - description: Authentication credential verified and session issued - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - '202': - description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + '201': + description: Invitation created successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Email OTP verification challenge (sign and retry) - value: - type: EMAIL_OTP - payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/UmaInvitation' '400': description: Bad request content: @@ -4745,106 +4649,96 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Authentication credential not found + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. + $ref: '#/components/schemas/Error500' + /invitations/{invitationCode}: + get: + summary: Get an UMA invitation by code + description: | + Retrieve details about an UMA invitation by its invitation code. + operationId: getInvitation + tags: + - Invitations + security: + - BasicAuth: [] + parameters: + - name: invitationCode + in: path + description: The code of the invitation to get + required: true + schema: + type: string + responses: + '200': + description: Invitation retrieved successfully + content: + application/json: schema: - type: integer + $ref: '#/components/schemas/UmaInvitation' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/Error401' + '404': + description: Invitation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/challenge: + /invitations/{invitationCode}/claim: post: - summary: Re-issue an authentication credential challenge + summary: Claim an UMA invitation description: | - Re-issue the challenge for an existing authentication credential. - - For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. + Claim an UMA invitation by associating it with an invitee UMA address. - `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + When an invitation is successfully claimed: + 1. The invitation status changes from PENDING to CLAIMED + 2. The invitee UMA address is associated with the invitation + 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation - For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. - operationId: challengeAuthCredential + This endpoint allows customers to accept invitations sent to them by other UMA customers. + operationId: claimInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] parameters: - - name: id + - name: invitationCode in: path - description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + description: The code of the invitation to claim required: true schema: type: string requestBody: - description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialChallengeRequest' - examples: - passkey: - summary: Re-challenge a passkey credential - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - emailOtp: - summary: Re-challenge an email-OTP credential (empty body) - value: {} - smsOtp: - summary: Re-challenge an SMS-OTP credential (empty body) - value: {} + $ref: '#/components/schemas/UmaInvitationClaimRequest' responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Invitation claimed successfully content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' - examples: - emailOtp: - summary: Email OTP challenge re-issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - passkey: - summary: Passkey reauthentication challenge issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/UmaInvitation' '400': description: Bad request content: @@ -4858,57 +4752,51 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Invitation not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions: - get: - summary: List active sessions - description: |- - Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. + /invitations/{invitationCode}/cancel: + post: + summary: Cancel an UMA invitation + description: | + Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. - operationId: listAuthSessions + When an invitation is cancelled: + 1. The invitation status changes from PENDING to CANCELLED + 2. The invitation can no longer be claimed + 3. The invitation URL will show as cancelled when accessed + + Only pending invitations can be cancelled. Attempting to cancel an invitation + that is already claimed, expired, or cancelled will result in an error. + operationId: cancelInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] parameters: - - name: accountId - in: query - description: Internal account id whose sessions to list. + - name: invitationCode + in: path + description: The code of the invitation to cancel required: true schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. + description: Invitation cancelled successfully content: application/json: schema: - $ref: '#/components/schemas/SessionListResponse' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) content: application/json: schema: @@ -4919,58 +4807,48 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Only the platform which created the invitation can cancel it + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Invitation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}: - delete: - summary: Revoke an authentication session + /sandbox/send: + post: + summary: Simulate sending funds description: | - Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: - - 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. - operationId: revokeAuthSession + Simulate sending funds to the bank account as instructed in the quote. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxSend tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the session to revoke. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxSendRequest' responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. + '200': + description: Funds received successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Session revoked successfully. + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request content: @@ -4978,13 +4856,19 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Session not found + description: Quote not found content: application/json: schema: @@ -4995,87 +4879,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}/refresh: + /sandbox/uma/receive: post: - summary: Refresh an authentication session + summary: Simulate payment send to test receiving an UMA payment description: | - Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: - - 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. - - The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. - operationId: refreshAuthSession + Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxReceive tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the active session to refresh. - required: true - schema: - type: string - example: Session:019542f5-b3e7-1d02-0000-000000000003 - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthSessionRefreshRequest' - examples: - refresh: - summary: Refresh an active session - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '201': - description: New authentication session created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - examples: - session: - summary: Refreshed authentication session - value: - id: Session:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - expiresAt: '2026-04-08T15:50:00Z' - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + '200': + description: Payment triggered successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Session refresh challenge - value: - payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/IncomingTransaction' '400': description: Bad request content: @@ -5083,13 +4910,19 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Session not found + description: Sender or receiver not found content: application/json: schema: @@ -5100,80 +4933,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys: + /sandbox/internal-accounts/{accountId}/fund: post: - summary: Create a delegated signing key + summary: Simulate funding an internal account description: | - Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: - - 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. - - 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. - - The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. - - Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. - operationId: createDelegatedKey + Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxFundInternalAccount tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + - name: accountId + in: path + required: true + description: The ID of the internal account to fund schema: type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeyCreateRequest' + $ref: '#/components/schemas/SandboxFundRequest' examples: - create: - summary: Delegate signing to a Grid-custodied key + fundUSDAccount: + summary: Fund USD internal account with $1,000 value: - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: Card payments key + amount: 100000 + fundBTCAccount: + summary: Fund BTC internal account with 0.01 BTC + value: + amount: 1000000 responses: - '201': - description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKey' - examples: - delegatedKey: - summary: Active delegated key - value: - id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 - nickname: Card payments key - status: ACTIVE - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:42Z' - '202': - description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + '200': + description: Internal account funded successfully content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + $ref: '#/components/schemas/InternalAccount' '400': description: Bad request content: @@ -5181,65 +4981,92 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card, card funding source, or Embedded Wallet funding account not found + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + $ref: '#/components/schemas/Error403' + '404': + description: Internal account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /uma-providers: get: - summary: List delegated signing keys - description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. - operationId: listDelegatedKeys + summary: List available Counterparty Providers + description: | + Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. + operationId: getAvailableUmaProviders tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] + - Available UMA Providers parameters: - - name: accountId + - in: query + name: countryCode + description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. + required: false + schema: + type: string + example: US + - in: query + name: currencyCode + description: The ISO 4217 currency code to filter providers by supported currency. + required: false + schema: + type: string + example: USD + - in: query + name: hasBlockedProviders + description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. + required: false + schema: + type: boolean + - name: limit in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) required: false - description: The id of the internal account whose delegated keys to list. schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - name: fundingSourceId + - name: sortOrder in: query + description: Order to sort results in required: false - description: The id of the card funding source whose delegated keys to list. schema: type: string - example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + enum: + - asc + - desc + default: desc + security: + - BasicAuth: [] responses: '200': - description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyListResponse' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/UmaProviderListResponse' '401': description: Unauthorized content: @@ -5252,119 +5079,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys/{id}: - get: - summary: Get a delegated signing key - description: Retrieve a delegated signing key by its system-generated id. - operationId: getDelegatedKeyById + /tokens: + post: + summary: Create a new API token + description: Create a new API token to access the Grid APIs. + operationId: createToken tags: - - Embedded Wallet Auth + - API Tokens security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '200': - description: Successful operation + '201': + description: API token created successfully content: application/json: schema: - $ref: '#/components/schemas/DelegatedKey' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Delegated key not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Revoke a delegated signing key - description: | - Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. - - The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. - operationId: revokeDelegatedKey - tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - responses: - '204': - description: Delegated key revoked. The key can no longer authorize signing. - '400': - description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized. - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Delegated key not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents: - post: - summary: Create an agent - description: | - Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. - operationId: createAgent - tags: - - Agent Management - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentCreateRequest' - responses: - '201': - description: Agent created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentCreateResponse' - '400': - description: Bad request + $ref: '#/components/schemas/ApiToken' + '400': + description: Bad request content: application/json: schema: @@ -5382,56 +5120,46 @@ paths: schema: $ref: '#/components/schemas/Error500' get: - summary: List agents - description: Retrieve a paginated list of agents for the authenticated platform. - operationId: listAgents + summary: List tokens + description: | + Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match + the specified filters. If no filters are provided, returns all tokens (paginated). + operationId: listTokens tags: - - Agent Management + - API Tokens security: - BasicAuth: [] parameters: - - name: customerId + - name: name in: query - description: Filter by customer ID + description: Filter by name of the token required: false schema: type: string - - name: isPaused - in: query - description: Filter by paused status - required: false - schema: - type: boolean - - name: isConnected - in: query - description: Filter by connection status (whether the device code has been redeemed) - required: false - schema: - type: boolean - name: createdAfter in: query - description: Filter agents created after this timestamp (inclusive) + description: Filter customers created after this timestamp (inclusive) required: false schema: type: string format: date-time - name: createdBefore in: query - description: Filter agents created before this timestamp (inclusive) + description: Filter customers created before this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedAfter in: query - description: Filter agents updated after this timestamp (inclusive) + description: Filter customers updated after this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedBefore in: query - description: Filter agents updated before this timestamp (inclusive) + description: Filter customers updated before this timestamp (inclusive) required: false schema: type: string @@ -5457,7 +5185,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentListResponse' + $ref: '#/components/schemas/TokenListResponse' '400': description: Bad request - Invalid parameters content: @@ -5476,227 +5204,192 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/approvals: + /tokens/{tokenId}: + parameters: + - name: tokenId + in: path + description: System-generated unique token identifier + required: true + schema: + type: string get: - summary: List agent transaction approval requests - description: | - Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: listAgentApprovals + summary: Get API token by ID + description: Retrieve an API token by their system-generated ID + operationId: getTokenById tags: - - Agent Management + - API Tokens security: - BasicAuth: [] - parameters: - - name: agentId - in: query - description: Filter by agent ID - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ApiToken' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Token not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me: - get: - summary: Get current agent - description: | - Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. - operationId: getAgentMe + delete: + summary: Delete API token by ID + description: Delete an API token by their system-generated ID + operationId: deleteTokenById tags: - - Agent Operations + - API Tokens security: - - AgentAuth: [] + - BasicAuth: [] responses: - '200': - description: Successful operation + '204': + description: API token deleted successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Token not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}: + /internal-accounts/{id}: parameters: - - name: agentId + - name: id in: path - description: System-generated unique agent identifier + description: The id of the internal account to update. required: true schema: type: string - get: - summary: Get agent by ID - description: Retrieve an agent by its system-generated ID. - operationId: getAgentById + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account + description: | + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating wallet privacy is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - patch: - summary: Update agent - description: Update an agent's name or paused state. - operationId: updateAgent - tags: - - Agent Management + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentUpdateRequest' + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true responses: '200': - description: Agent updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + description: Signed retry accepted. Returns the updated internal account. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + status: ACTIVE + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent - description: Permanently delete an agent. Connected agent software will lose access immediately. - operationId: deleteAgent - tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '204': - description: Agent deleted successfully + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Internal account not found content: application/json: schema: @@ -5707,36 +5400,69 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/policy: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - patch: - summary: Update agent policy + /internal-accounts/{id}/export: + post: + summary: Export internal account wallet credentials description: | - Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. - operationId: updateAgentPolicy + Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. + + Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): + + 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. + + The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. + operationId: exportInternalAccount tags: - - Agent Management + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the internal account to export. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentPolicyUpdateRequest' + $ref: '#/components/schemas/InternalAccountExportRequest' + examples: + export: + summary: Export request (both steps) + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 responses: '200': - description: Agent policy updated successfully + description: Signed retry accepted. Returns the encrypted wallet credentials. content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/InternalAccountExportResponse' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': description: Bad request content: @@ -5744,13 +5470,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Internal account not found content: application/json: schema: @@ -5761,161 +5487,233 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/device-codes: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string + /auth/credentials: post: - summary: Regenerate a device code + summary: Create an authentication credential description: | - Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. - operationId: regenerateAgentDeviceCode + Register an authentication credential for an Embedded Wallet customer. + + Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. + + Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. + operationId: createAuthCredential tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] - responses: - '201': - description: New device code generated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentDeviceCode' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Agent already has an active connection and cannot regenerate a device code - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' + examples: + emailOtp: + summary: Add an email OTP credential + value: + type: EMAIL_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + smsOtp: + summary: Add an SMS OTP credential + value: + type: SMS_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oauth: + summary: Add an OAuth credential + value: + type: OAUTH + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + passkey: + summary: Add a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: iPhone Face-ID + challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx + attestation: + credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY + clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow + transports: + - internal + - hybrid + responses: + '201': + description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: - $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/approve: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to approve - required: true - schema: - type: string - post: - summary: Approve an agent action - description: | - Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. - For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: approveAgentAction - tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '200': - description: Action approved successfully. Returns the updated AgentAction. + $ref: '#/components/schemas/AuthMethodResponse' + examples: + emailOtp: + summary: Email OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + smsOtp: + summary: SMS OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: SMS_OTP + nickname: '+14155550123' + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + oauth: + summary: OAuth credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + passkey: + summary: Passkey credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + '202': + description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Additional email OTP credential challenge + value: + type: EMAIL_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + smsOtp: + summary: Additional SMS OTP credential challenge + value: + type: SMS_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + passkey: + summary: Additional passkey credential challenge + value: + type: PASSKEY + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Action cannot be approved + description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent or action not found + description: Internal account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/reject: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to reject - required: true - schema: - type: string - post: - summary: Reject an agent action - description: | - Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: rejectAgentAction + get: + summary: List authentication credentials + description: |- + Retrieve all authentication credentials registered on an Embedded Wallet internal account. + + The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. + operationId: listAuthCredentials tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionRejectRequest' + parameters: + - name: accountId + in: query + description: Internal account id whose authentication credentials to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Action rejected successfully. Returns the updated AgentAction. + description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AuthCredentialListResponse' + examples: + multipleCredentials: + summary: Internal account with multiple authentication credentials + value: + data: + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:35:00Z' + updatedAt: '2026-04-08T15:35:00Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-09T10:15:00Z' + updatedAt: '2026-04-09T10:15:00Z' + empty: + summary: No credentials registered on the account + value: + data: [] '400': - description: Bad request - Action cannot be rejected + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: @@ -5926,56 +5724,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent or action not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/status: - parameters: - - name: code - in: path - description: The device code to check - required: true - schema: - type: string - get: - summary: Get device code status + /auth/credentials/{id}: + delete: + summary: Revoke an authentication credential description: | - Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. - operationId: getAgentDeviceCodeStatus + Revoke an authentication credential on an Embedded Wallet internal account. + + Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: + + 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. + operationId: revokeAuthCredential tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Successful operation + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Authentication credential revoked successfully. + '400': + description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Device code not found + description: Authentication credential not found content: application/json: schema: @@ -5986,143 +5802,212 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/redeem: - parameters: - - name: code - in: path - description: The device code to redeem - required: true - schema: - type: string + /auth/credentials/{id}/verify: post: - summary: Redeem device code + summary: Verify an authentication credential description: | - Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. - This endpoint does not require platform authentication — the device code itself serves as proof of authorization. - operationId: redeemAgentDeviceCode + Complete the verification step for a previously created authentication credential and issue a session. + + For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. + + For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. + + On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. + operationId: verifyAuthCredential tags: - - Agent Management - security: [] + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' + examples: + emailOtp: + summary: Verify an email OTP credential (first leg) + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + smsOtp: + summary: Verify an SMS OTP credential (first leg) + value: + type: SMS_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + emailOtpSignedRetry: + summary: Signed retry completing an email OTP verification + description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + oauth: + summary: Verify an OAuth credential + value: + type: OAUTH + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + passkey: + summary: Verify a passkey credential + value: + type: PASSKEY + assertion: + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 + authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA + signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Device code redeemed successfully + description: Authentication credential verified and session issued content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + $ref: '#/components/schemas/AuthSession' + '202': + description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Email OTP verification challenge (sign and retry) + value: + type: EMAIL_OTP + payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request (e.g., code already redeemed or expired) + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '404': - description: Device code not found + description: Authentication credential not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions: - get: - summary: List agent transactions + /auth/credentials/{id}/challenge: + post: + summary: Re-issue an authentication credential challenge description: | - Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. - operationId: agentListTransactions + Re-issue the challenge for an existing authentication credential. + + For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. + + `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + + For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. + operationId: challengeAuthCredential tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference - required: false + - name: id + in: path + description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true schema: type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc + requestBody: + description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialChallengeRequest' + examples: + passkey: + summary: Re-challenge a passkey credential + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + emailOtp: + summary: Re-challenge an email-OTP credential (empty body) + value: {} + smsOtp: + summary: Re-challenge an SMS-OTP credential (empty body) + value: {} responses: '200': - description: Successful operation + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/AuthCredentialResponseOneOf' + examples: + emailOtp: + summary: Email OTP challenge re-issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + passkey: + summary: Passkey reauthentication challenge issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -6133,155 +6018,239 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Authentication credential not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string + /auth/sessions: get: - summary: Get agent transaction by ID - description: | - Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. - operationId: agentGetTransaction + summary: List active sessions + description: |- + Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. + + The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. + operationId: listAuthSessions tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] + parameters: + - name: accountId + in: query + description: Internal account id whose sessions to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Successful operation + description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '401': - description: Unauthorized + $ref: '#/components/schemas/SessionListResponse' + '400': + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes: - post: - summary: Create a transfer quote + /auth/sessions/{id}: + delete: + summary: Revoke an authentication session description: | - Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. - If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. - operationId: agentCreateQuote + Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: + + 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. + operationId: revokeAuthSession tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key + - name: id + in: path + description: The id of the session to revoke. + required: true + schema: + type: string + - name: Grid-Wallet-Signature in: header required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. schema: type: string - example: - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/QuoteRequest' + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '201': - description: Transfer quote created successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Session revoked successfully. '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Session not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}: - parameters: - - name: quoteId - in: path - description: ID of the quote to retrieve - required: true - schema: - type: string - get: - summary: Get agent quote by ID + /auth/sessions/{id}/refresh: + post: + summary: Refresh an authentication session description: | - Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. - operationId: agentGetQuote + Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: + + 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. + + The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. + operationId: refreshAuthSession tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] - responses: - '200': - description: Quote retrieved successfully + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the active session to refresh. + required: true + schema: + type: string + example: Session:019542f5-b3e7-1d02-0000-000000000003 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSessionRefreshRequest' + examples: + refresh: + summary: Refresh an active session + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + responses: + '201': + description: New authentication session created successfully. content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/AuthSession' + examples: + session: + summary: Refreshed authentication session + value: + id: Session:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + expiresAt: '2026-04-08T15:50:00Z' + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Session refresh challenge + value: + payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Session not found content: application/json: schema: @@ -6292,75 +6261,100 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}/execute: - parameters: - - name: quoteId - in: path - required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 + /auth/delegated-keys: post: - summary: Execute a quote + summary: Create a delegated signing key description: | - Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. - If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - Once executed, the quote cannot be cancelled. - operationId: agentExecuteQuote + Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. + + Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key + - name: Grid-Wallet-Signature in: header required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. schema: type: string - example: - - name: Grid-Wallet-Signature + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id in: header required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyCreateRequest' + examples: + create: + summary: Delegate signing to a Grid-custodied key + value: + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Card payments key responses: - '200': - description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Card payments key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' '400': - description: Bad request - Quote cannot be executed + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Card, card funding source, or Embedded Wallet funding account not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Quote already executed, expired, or in invalid state + description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. content: application/json: schema: @@ -6371,45 +6365,42 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions: get: - summary: List agent's own actions - description: | - Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. - operationId: agentListActions + summary: List delegated signing keys + description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. + operationId: listDelegatedKeys tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: status - in: query - description: Filter by action status - required: false - schema: - $ref: '#/components/schemas/AgentActionStatus' - - name: limit + - name: accountId in: query - description: Maximum number of results to return (default 20, max 100) required: false + description: The id of the internal account whose delegated keys to list. schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: fundingSourceId in: query - description: Cursor for pagination (returned from previous request) required: false + description: The id of the card funding source whose delegated keys to list. schema: type: string + example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Successful operation + description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6422,30 +6413,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions/{actionId}: - parameters: - - name: actionId - in: path - description: Unique identifier of the agent action - required: true - schema: - type: string + /auth/delegated-keys/{id}: get: - summary: Get an agent action - description: | - Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. - operationId: agentGetAction + summary: Get a delegated signing key + description: Retrieve a delegated signing key by its system-generated id. + operationId: getDelegatedKeyById tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/DelegatedKey' '401': description: Unauthorized content: @@ -6453,7 +6444,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Action not found + description: Delegated key not found content: application/json: schema: @@ -6464,69 +6455,42 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: - post: - summary: Create a transfer-in + delete: + summary: Revoke a delegated signing key description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn + Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. + + The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. + operationId: revokeDelegatedKey tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true schema: type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' + '204': + description: Delegated key revoked. The key can no longer authorize signing. '400': - description: Bad request - Invalid parameters + description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Account not found + description: Delegated key not found content: application/json: schema: @@ -6537,50 +6501,31 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: + /agents: post: - summary: Create a transfer-out + summary: Create an agent description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut + Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. + operationId: createAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 + - BasicAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 + $ref: '#/components/schemas/AgentCreateRequest' responses: '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + description: Agent created successfully content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AgentCreateResponse' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -6591,47 +6536,67 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/internal-accounts: get: - summary: List agent's internal accounts - description: | - Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. - operationId: agentListInternalAccounts + summary: List agents + description: Retrieve a paginated list of agents for the authenticated platform. + operationId: listAgents tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: currency + - name: customerId in: query - description: Filter by currency code + description: Filter by customer ID required: false schema: type: string - - name: type + - name: isPaused in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. + description: Filter by paused status required: false schema: - $ref: '#/components/schemas/InternalAccountType' + type: boolean + - name: isConnected + in: query + description: Filter by connection status (whether the device code has been redeemed) + required: false + schema: + type: boolean + - name: createdAfter + in: query + description: Filter agents created after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: createdBefore + in: query + description: Filter agents created before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedAfter + in: query + description: Filter agents updated after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedBefore + in: query + description: Filter agents updated before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6653,7 +6618,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/AgentListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6666,23 +6637,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts: + /agents/approvals: get: - summary: List agent external accounts + summary: List agent transaction approval requests description: | - Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentListExternalAccounts + Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: listAgentApprovals tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: currency + - name: agentId in: query - description: Filter by currency code + description: Filter by agent ID + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format required: false schema: type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6698,13 +6689,23 @@ paths: required: false schema: type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/AgentActionListResponse' '400': description: Bad request - Invalid parameters content: @@ -6723,105 +6724,103 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add an external account + /agents/me: + get: + summary: Get current agent description: | - Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. - operationId: agentCreateExternalAccount + Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. + operationId: getAgentMe tags: - Agent Operations security: - AgentAuth: [] - requestBody: + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Agent' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + schema: + type: string + get: + summary: Get agent by ID + description: Retrieve an agent by its system-generated ID. + operationId: getAgentById + tags: + - Agent Management + security: + - BasicAuth: [] responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Agent' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - External account already exists + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get agent external account by ID - description: | - Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentGetExternalAccount + patch: + summary: Update agent + description: Update an agent's name or paused state. + operationId: updateAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentUpdateRequest' responses: '200': - description: Successful operation + description: Agent updated successfully content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/Agent' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6829,7 +6828,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Agent not found content: application/json: schema: @@ -6841,17 +6840,16 @@ paths: schema: $ref: '#/components/schemas/Error500' delete: - summary: Delete agent external account - description: | - Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentDeleteExternalAccount + summary: Delete agent + description: Permanently delete an agent. Connected agent software will lose access immediately. + operationId: deleteAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] responses: '204': - description: External account deleted successfully + description: Agent deleted successfully '401': description: Unauthorized content: @@ -6859,7 +6857,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Agent not found content: application/json: schema: @@ -6870,18 +6868,21 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /cards: - post: - summary: Issue a card + /agents/{agentId}/policy: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + patch: + summary: Update agent policy description: | - Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - - If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. - - New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). - operationId: createCard + Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. + operationId: updateAgentPolicy tags: - - Cards + - Agent Management security: - BasicAuth: [] requestBody: @@ -6889,25 +6890,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CardCreateRequest' - examples: - virtualCard: - summary: Issue a virtual card with one funding source - value: - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + $ref: '#/components/schemas/AgentPolicyUpdateRequest' responses: - '201': - description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. + '200': + description: Agent policy updated successfully content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/Agent' '400': - description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. + description: Bad request content: application/json: schema: @@ -6918,86 +6910,44 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error501' - get: - summary: List cards + $ref: '#/components/schemas/Error500' + /agents/{agentId}/device-codes: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + post: + summary: Regenerate a device code description: | - Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. - operationId: listCards + Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. + operationId: regenerateAgentDeviceCode tags: - - Cards + - Agent Management security: - BasicAuth: [] - parameters: - - name: cardholderId - in: query - description: Filter by cardholder (customer) id. - required: false - schema: - type: string - - name: accountId - in: query - description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. - required: false - schema: - type: string - - name: platformCardId - in: query - description: Filter by platform-specific card identifier. - required: false - schema: - type: string - - name: state - in: query - description: Filter by card state. - required: false - schema: - $ref: '#/components/schemas/CardState' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: - '200': - description: Successful operation + '201': + description: New device code generated successfully content: application/json: schema: - $ref: '#/components/schemas/CardListResponse' + $ref: '#/components/schemas/AgentDeviceCode' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -7008,41 +6958,62 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Agent already has an active connection and cannot regenerate a device code content: application/json: schema: - $ref: '#/components/schemas/Error501' - /cards/{id}: - parameters: - - name: id - in: path - description: System-generated unique card identifier + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}/actions/{actionId}/approve: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier required: true schema: type: string - get: - summary: Get a card - description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. - operationId: getCardById + - name: actionId + in: path + description: Unique identifier of the agent action to approve + required: true + schema: + type: string + post: + summary: Approve an agent action + description: | + Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. + For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: approveAgentAction tags: - - Cards + - Agent Management security: - BasicAuth: [] responses: '200': - description: Successful operation + description: Action approved successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/AgentAction' + '400': + description: Bad request - Action cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -7050,128 +7021,80 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Card not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Action is not pending approval or has already been processed content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error501' - patch: - summary: Update a card + $ref: '#/components/schemas/Error500' + /agents/{agentId}/actions/{actionId}/reject: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to reject + required: true + schema: + type: string + post: + summary: Reject an agent action description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - - - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - - Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): - - 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. - - Effects: - - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - - `state: ACTIVE`: normal authorization behavior resumes. - - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. - - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. - operationId: updateCardById + Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: rejectAgentAction tags: - - Cards + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/CardUpdateRequest' - examples: - freeze: - summary: Freeze an active card - value: - state: FROZEN - unfreeze: - summary: Unfreeze a frozen card - value: - state: ACTIVE - updateFundingSources: - summary: Replace the card's bound funding sources - value: - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - freezeAndUpdateSources: - summary: Freeze the card and replace its funding sources in one call - value: - state: FROZEN - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - close: - summary: Permanently close the card - value: - state: CLOSED + $ref: '#/components/schemas/AgentActionRejectRequest' responses: '200': - description: Signed retry accepted. Returns the updated card. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '202': - description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + description: Action rejected successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. + description: Bad request - Action cannot be rejected content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Card not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: Conflict - Action is not pending approval or has already been processed content: application/json: schema: @@ -7182,54 +7105,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /cards/{id}/reveal: + /agents/device-codes/{code}/status: parameters: - - name: id + - name: code in: path - description: System-generated unique card identifier + description: The device code to check required: true schema: type: string - post: - summary: Reveal card details - description: |- - Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. - - Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. - - Every reveal is audit-logged with the requesting actor. - operationId: revealCard + get: + summary: Get device code status + description: | + Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. + operationId: getAgentDeviceCodeStatus tags: - - Cards + - Agent Management security: - BasicAuth: [] responses: '200': - description: Reveal URL minted. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/CardRevealResponse' + $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Card not found + description: Device code not found content: application/json: schema: @@ -7240,70 +7147,141 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + /agents/device-codes/{code}/redeem: + parameters: + - name: code + in: path + description: The device code to redeem + required: true + schema: + type: string + post: + summary: Redeem device code + description: | + Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. + This endpoint does not require platform authentication — the device code itself serves as proof of authorization. + operationId: redeemAgentDeviceCode + tags: + - Agent Management + security: [] + responses: + '200': + description: Device code redeemed successfully content: application/json: schema: - $ref: '#/components/schemas/Error501' - /sandbox/cards/{id}/simulate/authorization: - post: - summary: Simulate a card authorization + $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + '400': + description: Bad request (e.g., code already redeemed or expired) + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Device code not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/transactions: + get: + summary: List agent transactions description: | - Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - - The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - - | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorization - tags: - - Sandbox + Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. + operationId: agentListTransactions + tags: + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - required: true - description: The id of the card to simulate an authorization against. + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) + required: false schema: type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - coffeeAuth: - summary: Approved $12.50 auth at a coffee shop - value: - amount: 1250 - currency: - code: USD - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - declinedInsufficientFunds: - summary: Declined — insufficient funds (descriptor suffix `002`) - value: - amount: 50000 - currency: - code: USD - merchant: - descriptor: AMAZON RETAIL US-002 - mcc: '5942' - country: US + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Simulated authorization processed. Returns the resulting card transaction. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/CardTransaction' + $ref: '#/components/schemas/TransactionListResponse' '400': description: Bad request - Invalid parameters content: @@ -7316,92 +7294,44 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/clearing: - post: - summary: Simulate a card clearing + /agents/me/transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get agent transaction by ID description: | - Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. - - - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). - - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. - - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). - - Production returns `404` on this path. - operationId: sandboxSimulateCardClearing + Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. + operationId: agentGetTransaction tags: - - Sandbox + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the clearing applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardClearingRequest' - examples: - tipOnTopClearing: - summary: Clearing larger than auth — exercises post-hoc pull - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 - authorizationExpiry: - summary: Clearing of 0 — exercises authorization expiry - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 0 + - AgentAuth: [] responses: '200': - description: Simulated clearing processed. Returns the updated card transaction. - content: - application/json: - schema: - $ref: '#/components/schemas/CardTransaction' - '400': - description: Bad request - Invalid parameters + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Card or card transaction not found + description: Transaction not found content: application/json: schema: @@ -7412,47 +7342,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return: + /agents/me/quotes: post: - summary: Simulate a card return + summary: Create a transfer quote description: | - Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). - - Production returns `404` on this path. - operationId: sandboxSimulateCardReturn + Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. + If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. + operationId: agentCreateQuote tags: - - Sandbox + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - required: true - description: The id of the card the return applies to. + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardReturnRequest' - examples: - fullRefund: - summary: Full refund of a $15.00 settled transaction - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 + $ref: '#/components/schemas/QuoteRequest' responses: - '200': - description: Simulated return processed. Returns the updated card transaction. + '201': + description: Transfer quote created successfully content: application/json: schema: - $ref: '#/components/schemas/CardTransaction' + $ref: '#/components/schemas/Quote' '400': - description: Bad request - Invalid parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -7464,74 +7388,140 @@ paths: schema: $ref: '#/components/schemas/Error401' '403': - description: Forbidden - request was made with a production platform token + description: Forbidden - Agent policy does not permit this operation content: application/json: schema: $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins: - post: - summary: Register an existing provider-created stablecoin - description: | - Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. - operationId: registerStablecoin - tags: - - Stablecoins + /agents/me/quotes/{quoteId}: + parameters: + - name: quoteId + in: path + description: ID of the quote to retrieve + required: true + schema: + type: string + get: + summary: Get agent quote by ID + description: | + Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. + operationId: agentGetQuote + tags: + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + responses: + '200': + description: Quote retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/quotes/{quoteId}/execute: + parameters: + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Execute a quote + description: | + Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. + If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + Once executed, the quote cannot be cancelled. + operationId: agentExecuteQuote + tags: + - Agent Operations + security: + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinRegisterRequest' + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 responses: - '201': - description: Stablecoin registered + '200': + description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' content: application/json: schema: - $ref: '#/components/schemas/Stablecoin' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Quote cannot be executed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Referenced stablecoin provider account not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict + description: Conflict - Quote already executed, expired, or in invalid state content: application/json: schema: @@ -7542,30 +7532,23 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/actions: get: - summary: List stablecoins - description: Retrieve stablecoins registered to the authenticated platform. - operationId: listStablecoins + summary: List agent's own actions + description: | + Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. + operationId: agentListActions tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: issuanceStatus - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinIssuanceStatus' - - name: gridOperationsStatus - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinGridOperationsStatus' - - name: provider + - name: status in: query + description: Filter by action status required: false schema: - $ref: '#/components/schemas/StablecoinProvider' + $ref: '#/components/schemas/AgentActionStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7577,7 +7560,7 @@ paths: default: 20 - name: cursor in: query - description: Opaque cursor returned as `nextCursor` from the previous response. + description: Cursor for pagination (returned from previous request) required: false schema: type: string @@ -7587,13 +7570,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StablecoinListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentActionListResponse' '401': description: Unauthorized content: @@ -7606,29 +7583,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins/{stablecoinId}: + /agents/me/actions/{actionId}: parameters: - - name: stablecoinId + - name: actionId in: path - description: System-generated stablecoin identifier + description: Unique identifier of the agent action required: true schema: type: string get: - summary: Get a stablecoin - description: Retrieve a single registered stablecoin by its Grid identifier. - operationId: getStablecoin + summary: Get an agent action + description: | + Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. + operationId: agentGetAction tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Stablecoin' + $ref: '#/components/schemas/AgentAction' '401': description: Unauthorized content: @@ -7636,7 +7614,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Stablecoin not found + description: Action not found content: application/json: schema: @@ -7647,46 +7625,51 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins/{stablecoinId}/mints: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/transfer-in: post: - summary: Create a stablecoin mint operation + summary: Create a transfer-in description: | - Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. - operationId: createStablecoinMint + Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. + operationId: agentCreateTransferIn tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this mint request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinMintRequest' + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: '201': - description: Mint operation created + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -7697,70 +7680,68 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin or referenced account not found + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict + $ref: '#/components/schemas/Error403' + '404': + description: Account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '503': - description: Provider temporarily unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/Error503' - /stablecoins/{stablecoinId}/burns: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/transfer-out: post: - summary: Create a stablecoin burn operation + summary: Create a transfer-out description: | - Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. - operationId: createStablecoinBurn + Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + operationId: agentCreateTransferOut tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this burn request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinBurnRequest' - responses: - '201': - description: Burn operation created - content: - application/json: + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + amount: 12550 + responses: + '201': + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + content: + application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -7771,53 +7752,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin or referenced account not found + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict + $ref: '#/components/schemas/Error403' + '404': + description: Account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '503': - description: Provider temporarily unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/Error503' - /stablecoins/{stablecoinId}/operations: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/internal-accounts: get: - summary: List stablecoin issuer operations - description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. - operationId: listStablecoinOperations + summary: List agent's internal accounts + description: | + Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. + operationId: agentListInternalAccounts tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: operationType + - name: currency in: query - description: Filter operations by type. When omitted, both mints and burns are returned. + description: Filter by currency code required: false schema: - $ref: '#/components/schemas/StablecoinOperationType' + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7829,7 +7804,7 @@ paths: default: 20 - name: cursor in: query - description: Opaque cursor returned as `nextCursor` from the previous response. + description: Cursor for pagination (returned from previous request) required: false schema: type: string @@ -7839,97 +7814,127 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperationListResponse' + $ref: '#/components/schemas/InternalAccountListResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-operations/{stablecoinOperationId}: - parameters: - - name: stablecoinOperationId - in: path - description: System-generated stablecoin issuer operation identifier - required: true - schema: - type: string + /agents/me/external-accounts: get: - summary: Get a stablecoin issuer operation - description: Retrieve a single stablecoin issuer operation by its Grid identifier. - operationId: getStablecoinOperation + summary: List agent external accounts + description: | + Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentListExternalAccounts tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' - '401': - description: Unauthorized + $ref: '#/components/schemas/ExternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin operation not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts: post: - summary: Link a stablecoin provider account + summary: Add an external account description: | - Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. - operationId: linkStablecoinProviderAccount + Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. + operationId: agentCreateExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. - required: true - schema: - type: string - maxLength: 255 + - AgentAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: '201': - description: Stablecoin provider account linked + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccount' + $ref: '#/components/schemas/ExternalAccount' '400': description: Bad request content: @@ -7943,7 +7948,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict + description: Conflict - External account already exists content: application/json: schema: @@ -7954,88 +7959,60 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string get: - summary: List stablecoin provider account links - description: Retrieve stablecoin provider account links for the authenticated platform. - operationId: listStablecoinProviderAccounts + summary: Get agent external account by ID + description: | + Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentGetExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: provider - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProvider' - - name: status - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProviderAccountStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts/{stablecoinProviderAccountId}: - parameters: - - name: stablecoinProviderAccountId - in: path - description: System-generated stablecoin provider account link identifier - required: true - schema: - type: string - get: - summary: Get a stablecoin provider account link - description: Retrieve a stablecoin provider account link by id for the authenticated platform. - operationId: getStablecoinProviderAccount + delete: + summary: Delete agent external account + description: | + Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentDeleteExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -8043,7 +8020,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Stablecoin provider account link not found + description: External account not found content: application/json: schema: @@ -8054,1189 +8031,3167 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' -webhooks: - agent-action: + /cards: post: - summary: Agent action pending approval webhook + summary: Issue a card description: | - Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. - The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: agentActionWebhook + New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). + operationId: createCard tags: - - Webhooks + - Cards security: - - WebhookSignature: [] + - BasicAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentActionWebhook' + $ref: '#/components/schemas/CardCreateRequest' examples: - pendingApproval: - summary: Agent action pending approval + virtualCard: + summary: Issue a virtual card with one funding source value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: AGENT_ACTION.PENDING_APPROVAL - timestamp: '2025-10-03T15:00:00Z' - data: - id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 - agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 - platformCustomerId: user-a1b2c3 - status: PENDING_APPROVAL - type: EXECUTE_QUOTE - quote: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - expiresAt: '2025-10-03T15:00:30Z' - createdAt: '2025-10-03T15:00:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: INR - name: Indian Rupee - symbol: ₹ - decimals: 2 - totalSendingAmount: 50000 - totalReceivingAmount: 4625000 - exchangeRate: 92.5 - feesIncluded: 250 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 - createdAt: '2025-10-03T15:00:00Z' - updatedAt: '2025-10-03T15:00:00Z' + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '200': - description: Webhook received and acknowledged. + '201': + description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + '400': + description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - incoming-payment: - post: - summary: Incoming payment webhook and approval mechanism + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + get: + summary: List cards description: | - Webhook that is called when an incoming payment is received by a customer's UMA address. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Payment Approval Flow - When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: - - 1. The client should check the `counterpartyInformation` against their requirements - 2. To APPROVE the payment synchronously, return a 200 OK response - 3. To REJECT the payment, return a 403 Forbidden response with an Error object - 4. To request more information, return a 422 Unprocessable Entity with specific missing fields - 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. - - The Grid system will proceed or cancel the payment based on your response. - - For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. - operationId: incomingPaymentWebhook + Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. + operationId: listCards tags: - - Webhooks + - Cards security: - - WebhookSignature: [] - requestBody: + - BasicAuth: [] + parameters: + - name: cardholderId + in: query + description: Filter by cardholder (customer) id. + required: false + schema: + type: string + - name: accountId + in: query + description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. + required: false + schema: + type: string + - name: platformCardId + in: query + description: Filter by platform-specific card identifier. + required: false + schema: + type: string + - name: state + in: query + description: Filter by card state. + required: false + schema: + $ref: '#/components/schemas/CardState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CardListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}: + parameters: + - name: id + in: path + description: System-generated unique card identifier required: true - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhook' - examples: - pendingPayment: - summary: Pending payment example requiring approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.PENDING - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: PENDING - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - counterpartyInformation: - FULL_NAME: John Sender - BIRTH_DATE: '1985-06-15' - NATIONALITY: US - reconciliationInstructions: - reference: REF-123456789 - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: POSTAL_ADDRESS - mandatory: false - incomingCompletedPayment: - summary: Completed payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - incomingCompletedCryptoPayment: - summary: Completed payment funded from an external crypto wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - type: INCOMING - direction: CREDIT - source: - sourceType: REALTIME_FUNDING - currency: USDC - onChainTransaction: - transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf - network: SOLANA - destination: - destinationType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC deposit from self-custody wallet + schema: + type: string + get: + summary: Get a card + description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. + operationId: getCardById + tags: + - Cards + security: + - BasicAuth: [] responses: '200': - description: | - Webhook received successfully. - For PENDING transactions, this indicates approval to proceed with the payment. - If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookResponse' - '202': - description: | - Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in - cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. - The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Card' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: | - Forbidden - Payment rejected by the client. - Only applicable for PENDING transactions. + '404': + description: Card not found content: application/json: schema: - $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - '422': - description: | - Unprocessable Entity - Additional counterparty information required. - Only applicable for PENDING transactions. + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. content: application/json: schema: - $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' - outgoing-payment: - post: - summary: Outgoing payment status webhook + $ref: '#/components/schemas/Error501' + patch: + summary: Update a card description: | - Webhook that is called when an outgoing payment's status changes. - This endpoint should be implemented by clients of the Grid API. + Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. + - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): - This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. - operationId: outgoingPaymentWebhook + 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + + Effects: + - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. + - `state: ACTIVE`: normal authorization behavior resumes. + - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. + - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + + The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + operationId: updateCardById tags: - - Webhooks + - Cards security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/OutgoingPaymentWebhook' + $ref: '#/components/schemas/CardUpdateRequest' examples: - outgoingCompletedPayment: - summary: Completed outgoing payment + freeze: + summary: Freeze an active card value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivedAmount: - amount: 9706 - currency: - code: EUR - name: Euro - symbol: € - decimals: 2 - exchangeRate: 0.92 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' - paymentInstructions: [] - rateDetails: - counterpartyMultiplier: 1.08 - counterpartyFixedFee: 10 - gridApiMultiplier: 0.925 - gridApiFixedFee: 10 - gridApiVariableFeeRate: 0.003 - gridApiVariableFeeAmount: 30 - outgoingCompletedCryptoPayment: - summary: Completed crypto payout to an external wallet + state: FROZEN + unfreeze: + summary: Unfreeze a frozen card value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000009 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 - onChainTransaction: - transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx - network: SOLANA - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - sentAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC withdrawal to self-custody wallet - paymentInstructions: [] - failedPayment: - summary: Failed outgoing payment + state: ACTIVE + updateFundingSources: + summary: Replace the card's bound funding sources value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - failureReason: QUOTE_EXECUTION_FAILED + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + freezeAndUpdateSources: + summary: Freeze the card and replace its funding sources in one call + value: + state: FROZEN + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + close: + summary: Permanently close the card + value: + state: CLOSED responses: '200': - description: Webhook received successfully + description: Signed retry accepted. Returns the updated card. + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + '202': + description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request + description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Card not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - Webhook has already been processed (duplicate id) + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: $ref: '#/components/schemas/Error409' - test-webhook: + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}/reveal: + parameters: + - name: id + in: path + description: System-generated unique card identifier + required: true + schema: + type: string post: - summary: Test webhook for integration verification - description: | - Webhook that is sent once to verify your webhook endpoint is correctly set up. - This is sent when you configure or update your platform settings with a webhook URL. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + summary: Reveal card details + description: |- + Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. - This webhook is purely for testing your endpoint integration and signature verification. - operationId: testWebhook + Every reveal is audit-logged with the requesting actor. + operationId: revealCard tags: - - Webhooks + - Cards security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestWebhookRequest' - examples: - testWebhook: - summary: Test webhook example - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000001 - type: TEST - timestamp: '2025-08-15T14:32:00Z' - data: {} + - BasicAuth: [] responses: '200': - description: Webhook received successfully. This confirms your webhook endpoint is properly configured. - '400': - description: Bad request + description: Reveal URL minted. content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/CardRevealResponse' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). content: application/json: schema: - $ref: '#/components/schemas/Error409' - bulk-upload: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /sandbox/cards/{id}/simulate/authorization: post: - summary: Bulk upload status webhook + summary: Simulate a card authorization description: | - Webhook that is called when a bulk customer upload job completes or fails. - This endpoint should be implemented by clients of the Grid API. + Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. - operationId: bulkUploadWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate an authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/BulkUploadWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - completedUpload: - summary: Successful bulk upload completion + coffeeAuth: + summary: Approved $12.50 auth at a coffee shop value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - progress: - total: 5000 - processed: 5000 - successful: 5000 - failed: 0 - errors: [] - failedUpload: - summary: Failed bulk upload + amount: 1250 + currency: + code: USD + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + declinedInsufficientFunds: + summary: Declined — insufficient funds (descriptor suffix `002`) value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: FAILED - progress: - total: 5000 - processed: 5000 - successful: 0 - failed: 5000 - errors: - - correlationId: row_1 - error: - code: invalid_csv_format - message: Invalid CSV format - details: - reason: missing_required_column - column: umaAddress + amount: 50000 + currency: + code: USD + merchant: + descriptor: AMAZON RETAIL US-002 + mcc: '5942' + country: US responses: - '200': - description: Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - invitation-claimed: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/clearing: post: - summary: Invitation claimed webhook + summary: Simulate a card clearing description: | - Webhook that is called when an invitation is claimed by a customer. - This endpoint should be implemented by platform clients of the Grid API. - - When a customer claims an invitation, this webhook is triggered to notify the platform that: - 1. The invitation has been successfully claimed - 2. The invitee UMA address is now associated with the invitation - 3. The invitation status has changed from PENDING to CLAIMED - - This allows platforms to: - - Track invitation usage and conversion rates - - Trigger onboarding flows for new customers who joined via invitation - - Apply referral bonuses or rewards to the inviter - - Update their UI to reflect the claimed status + Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). + - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. + - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: invitationClaimedWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardClearing tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the clearing applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InvitationClaimedWebhook' + $ref: '#/components/schemas/SandboxCardClearingRequest' examples: - claimedInvitation: - summary: Invitation claimed notification + tipOnTopClearing: + summary: Clearing larger than auth — exercises post-hoc pull value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION.CLAIMED - timestamp: '2025-09-01T15:45:00Z' - data: - code: 019542f5 - createdAt: '2025-09-01T14:30:00Z' - claimedAt: '2025-09-01T15:45:00Z' - inviterUma: $inviter@uma.domain - inviteeUma: $invitee@uma.domain - status: CLAIMED - url: https://uma.me/i/019542f5 + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + authorizationExpiry: + summary: Clearing of 0 — exercises authorization expiry + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 0 responses: - '200': - description: Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - customer-update: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/return: post: - summary: Customer status change + summary: Simulate a card return description: | - Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. - This endpoint should be implemented by clients of the Grid API. + Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Production returns `404` on this path. + operationId: sandboxSimulateCardReturn + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the return applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardReturnRequest' + examples: + fullRefund: + summary: Full refund of a $15.00 settled transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/balance_inquiry: + post: + summary: Simulate a card balance inquiry + description: | + Simulate a balance-inquiry authorization against a card in the sandbox environment. A balance inquiry is always a `0`-amount authorization, so the request carries no `amount` — only the `merchant`. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: customerStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardBalanceInquiry tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a balance inquiry against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CustomerWebhook' + $ref: '#/components/schemas/SandboxCardBalanceInquiryRequest' examples: - kycApprovedWebhook: - summary: When an individual customer KYC has been approved + balanceInquiry: + summary: Balance inquiry at a fuel pump value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: CUSTOMER.KYC_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 9f84e0c2a72c4fa - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $john.doe@uma.domain.com - kycStatus: APPROVED - fullName: John Michael Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - line2: Apt 4B - city: San Francisco - state: CA - postalCode: '94105' - country: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-07-21T17:32:28Z' - isDeleted: false - kycRejectedWebhook: - summary: When an individual customer KYC has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: CUSTOMER.KYC_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000002 - platformCustomerId: 4b7c1e9d3f5a8e2 - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $jane.smith@uma.domain.com - kycStatus: REJECTED - fullName: Jane Smith - birthDate: '1988-03-22' - nationality: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybApprovedWebhook: - summary: When a business customer KYB has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: CUSTOMER.KYB_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000003 - platformCustomerId: 7a2f9d4e1b8c3f5 - customerType: BUSINESS - region: US - currencies: - - USD - - USDC - umaAddress: $acme.corp@uma.domain.com - kybStatus: APPROVED - address: - line1: 456 Business Ave - city: New York - state: NY - postalCode: '10001' - country: US - businessInfo: - legalName: Acme Corporation - registrationNumber: '12345678' - taxId: 98-7654321 - incorporatedOn: '2018-03-14' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybRejectedWebhook: - summary: When a business customer KYB has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000010 - type: CUSTOMER.KYB_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000004 - platformCustomerId: 3c8e5f2a9d1b7e4 - customerType: BUSINESS - region: US - currencies: - - USD - umaAddress: $globex.inc@uma.domain.com - kybStatus: REJECTED - address: - line1: 789 Corporate Blvd - city: Chicago - state: IL - postalCode: '60601' - country: US - businessInfo: - legalName: Globex Inc - taxId: 47-1234567 - incorporatedOn: '2015-09-01' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false + merchant: + descriptor: SHELL OIL 12345678 + mcc: '5541' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - internal-account-status: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/credit_authorization: post: - summary: Internal account status webhook + summary: Simulate a card credit authorization description: | - Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Simulate an inbound credit authorization (a merchant-initiated credit to the card, e.g. a refund pushed as an authorization) in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - ### Event types - - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. - - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. - operationId: internalAccountStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardCreditAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a credit authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountStatusWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - balanceDecrease: - summary: A transaction just cleared a customer account and the balance has decreased - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INTERNAL_ACCOUNT.BALANCE_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: ACTIVE - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12500 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - statusUpdated: - summary: The status of an internal account changed (e.g., frozen by Grid) + creditAuth: + summary: $25.00 credit authorization value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INTERNAL_ACCOUNT.STATUS_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: FROZEN - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' + amount: 2500 + currency: + code: USD + merchant: + descriptor: ACME MARKETPLACE + mcc: '5942' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - verification-update: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/financial_authorization: post: - summary: Verification status change + summary: Simulate a card financial authorization description: | - Webhook that is called when a customer's KYC/KYB verification status changes. - This endpoint should be implemented by clients of the Grid API. + Simulate a single-message financial authorization (an authorization that clears in the same message, e.g. an ATM withdrawal or other dual-message-exempt flow) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: verificationStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardFinancialAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a financial authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/VerificationWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - approved: - summary: Verification approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: VERIFICATION.APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: APPROVED - errors: [] - createdAt: '2025-08-15T14:00:00Z' - resolveErrors: - summary: Verification requires action + financialAuth: + summary: $80.00 single-message ATM withdrawal value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000031 - type: VERIFICATION.RESOLVE_ERRORS - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000011 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - createdAt: '2025-08-15T14:00:00Z' + amount: 8000 + currency: + code: USD + merchant: + descriptor: ATM WITHDRAWAL 24TH ST + mcc: '6011' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - card-state-change: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/financial_credit_authorization: post: - summary: Card state change + summary: Simulate a card financial credit authorization description: | - Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication + Simulate a single-message financial credit authorization (a credit to the card that clears in the same message, e.g. an ATM deposit or push credit) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardStateChangeWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardFinancialCreditAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a financial credit authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardStateChangeWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - activated: - summary: Card transitioned from PROCESSING to ACTIVE - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:11:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - processorRef: card_b81c2a4f - issuerRef: lead_card_7a1b9c3d - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:11:00Z' - issuerRejected: - summary: Card rejected by issuer during provisioning - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000021 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:12:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000011 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: CLOSED - stateReason: ISSUER_REJECTED - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:12:00Z' - frozen: - summary: Card frozen by the platform + financialCreditAuth: + summary: $40.00 single-message push credit value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000022 - type: CARD.STATE_CHANGE - timestamp: '2026-05-09T09:00:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: FROZEN - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-09T09:00:00Z' + amount: 4000 + currency: + code: USD + merchant: + descriptor: ACME PAYOUTS + mcc: '6012' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - card-funding-source-change: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/authorization_advice: post: - summary: Card funding source change + summary: Simulate a card authorization advice description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. + Simulate an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization on a `CardTransaction` in the sandbox environment. The `amount` is the new *total* authorized amount the advice re-sizes to. The parent transaction must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorizationAdvice tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the advice applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationAdviceRequest' examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} + resizeUp: + summary: Re-size an open auth up to a new total of $20.00 value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 2000 responses: - '200': - description: | + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/credit_authorization_advice: + post: + summary: Simulate a card credit authorization advice + description: | + Simulate a network-initiated `CREDIT_AUTHORIZATION_ADVICE` against a card in the sandbox environment. Like `simulate/credit_authorization`, it is keyed by the card (PAN) and `merchant` rather than an existing transaction. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + + Production returns `404` on this path. + operationId: sandboxSimulateCardCreditAuthorizationAdvice + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the advice applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + examples: + creditAdvice: + summary: $25.00 credit authorization advice + value: + amount: 2500 + currency: + code: USD + merchant: + descriptor: ACME MARKETPLACE + mcc: '5942' + country: US + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/return_reversal: + post: + summary: Simulate a card return reversal + description: | + Simulate a `RETURN_REVERSAL` against an existing `REFUNDED` card transaction in the sandbox environment — reversing a previously settled return. The parent transaction must be in `REFUNDED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. + + Note: authorization reversal / void has no dedicated card-issuer simulate path (the void simulator emits a distinct `VOID` event), so — like authorization expiry — it is not exposed here. + + Production returns `404` on this path. + operationId: sandboxSimulateCardReturnReversal + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the return reversal applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardTransactionRefRequest' + examples: + returnReversal: + summary: Reverse a settled return on a refunded transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins: + post: + summary: Register an existing provider-created stablecoin + description: | + Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. + operationId: registerStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinRegisterRequest' + responses: + '201': + description: Stablecoin registered + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Referenced stablecoin provider account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoins + description: Retrieve stablecoins registered to the authenticated platform. + operationId: listStablecoins + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: issuanceStatus + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinIssuanceStatus' + - name: gridOperationsStatus + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + - name: provider + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProvider' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins/{stablecoinId}: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin + description: Retrieve a single registered stablecoin by its Grid identifier. + operationId: getStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins/{stablecoinId}/mints: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin mint operation + description: | + Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinMint + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this mint request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinMintRequest' + responses: + '201': + description: Mint operation created + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/burns: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin burn operation + description: | + Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinBurn + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this burn request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinBurnRequest' + responses: + '201': + description: Burn operation created + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/operations: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + get: + summary: List stablecoin issuer operations + description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. + operationId: listStablecoinOperations + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: operationType + in: query + description: Filter operations by type. When omitted, both mints and burns are returned. + required: false + schema: + $ref: '#/components/schemas/StablecoinOperationType' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperationListResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-operations/{stablecoinOperationId}: + parameters: + - name: stablecoinOperationId + in: path + description: System-generated stablecoin issuer operation identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin issuer operation + description: Retrieve a single stablecoin issuer operation by its Grid identifier. + operationId: getStablecoinOperation + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin operation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-provider-accounts: + post: + summary: Link a stablecoin provider account + description: | + Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. + operationId: linkStablecoinProviderAccount + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' + responses: + '201': + description: Stablecoin provider account linked + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoin provider account links + description: Retrieve stablecoin provider account links for the authenticated platform. + operationId: listStablecoinProviderAccounts + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: provider + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProvider' + - name: status + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProviderAccountStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-provider-accounts/{stablecoinProviderAccountId}: + parameters: + - name: stablecoinProviderAccountId + in: path + description: System-generated stablecoin provider account link identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin provider account link + description: Retrieve a stablecoin provider account link by id for the authenticated platform. + operationId: getStablecoinProviderAccount + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccount' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin provider account link not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' +webhooks: + agent-action: + post: + summary: Agent action pending approval webhook + description: | + Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: agentActionWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentActionWebhook' + examples: + pendingApproval: + summary: Agent action pending approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: AGENT_ACTION.PENDING_APPROVAL + timestamp: '2025-10-03T15:00:00Z' + data: + id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 + agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 + platformCustomerId: user-a1b2c3 + status: PENDING_APPROVAL + type: EXECUTE_QUOTE + quote: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + expiresAt: '2025-10-03T15:00:30Z' + createdAt: '2025-10-03T15:00:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: INR + name: Indian Rupee + symbol: ₹ + decimals: 2 + totalSendingAmount: 50000 + totalReceivingAmount: 4625000 + exchangeRate: 92.5 + feesIncluded: 250 + platformFeesIncluded: 0 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 + createdAt: '2025-10-03T15:00:00Z' + updatedAt: '2025-10-03T15:00:00Z' + responses: + '200': + description: Webhook received and acknowledged. + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + incoming-payment: + post: + summary: Incoming payment webhook and approval mechanism + description: | + Webhook that is called when an incoming payment is received by a customer's UMA address. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Payment Approval Flow + When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: + + 1. The client should check the `counterpartyInformation` against their requirements + 2. To APPROVE the payment synchronously, return a 200 OK response + 3. To REJECT the payment, return a 403 Forbidden response with an Error object + 4. To request more information, return a 422 Unprocessable Entity with specific missing fields + 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. + + The Grid system will proceed or cancel the payment based on your response. + + For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. + operationId: incomingPaymentWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhook' + examples: + pendingPayment: + summary: Pending payment example requiring approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: PENDING + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + counterpartyInformation: + FULL_NAME: John Sender + BIRTH_DATE: '1985-06-15' + NATIONALITY: US + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: POSTAL_ADDRESS + mandatory: false + incomingCompletedPayment: + summary: Completed payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + incomingCompletedCryptoPayment: + summary: Completed payment funded from an external crypto wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + type: INCOMING + direction: CREDIT + source: + sourceType: REALTIME_FUNDING + currency: USDC + onChainTransaction: + transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf + network: SOLANA + destination: + destinationType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC deposit from self-custody wallet + incomingRefundedPayment: + summary: Refunded incoming payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000011 + type: INCOMING_PAYMENT.REFUND_COMPLETED + timestamp: '2025-08-15T14:40:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + failureReason: OFFRAMP_FAILED + refund: + reference: REF-123456789-REFUND + initiatedAt: '2025-08-15T14:35:00Z' + settledAt: '2025-08-15T14:40:00Z' + status: COMPLETED + reason: TRANSACTION_FAILED + responses: + '200': + description: | + Webhook received successfully. + For PENDING transactions, this indicates approval to proceed with the payment. + If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookResponse' + '202': + description: | + Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in + cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. + The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: | + Forbidden - Payment rejected by the client. + Only applicable for PENDING transactions. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '422': + description: | + Unprocessable Entity - Additional counterparty information required. + Only applicable for PENDING transactions. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' + outgoing-payment: + post: + summary: Outgoing payment status webhook + description: | + Webhook that is called when an outgoing payment's status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. + operationId: outgoingPaymentWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OutgoingPaymentWebhook' + examples: + outgoingCompletedPayment: + summary: Completed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivedAmount: + amount: 9706 + currency: + code: EUR + name: Euro + symbol: € + decimals: 2 + exchangeRate: 0.92 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: + counterpartyMultiplier: 1.08 + counterpartyFixedFee: 10 + gridApiMultiplier: 0.925 + gridApiFixedFee: 10 + gridApiVariableFeeRate: 0.003 + gridApiVariableFeeAmount: 30 + outgoingCompletedCryptoPayment: + summary: Completed crypto payout to an external wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000009 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 + onChainTransaction: + transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx + network: SOLANA + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + sentAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC withdrawal to self-custody wallet + paymentInstructions: [] + failedPayment: + summary: Failed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + failureReason: QUOTE_EXECUTION_FAILED + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + test-webhook: + post: + summary: Test webhook for integration verification + description: | + Webhook that is sent once to verify your webhook endpoint is correctly set up. + This is sent when you configure or update your platform settings with a webhook URL. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is purely for testing your endpoint integration and signature verification. + operationId: testWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestWebhookRequest' + examples: + testWebhook: + summary: Test webhook example + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 + type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} + responses: + '200': + description: Webhook received successfully. This confirms your webhook endpoint is properly configured. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + bulk-upload: + post: + summary: Bulk upload status webhook + description: | + Webhook that is called when a bulk customer upload job completes or fails. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. + operationId: bulkUploadWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BulkUploadWebhook' + examples: + completedUpload: + summary: Successful bulk upload completion + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + progress: + total: 5000 + processed: 5000 + successful: 5000 + failed: 0 + errors: [] + failedUpload: + summary: Failed bulk upload + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: FAILED + progress: + total: 5000 + processed: 5000 + successful: 0 + failed: 5000 + errors: + - correlationId: row_1 + error: + code: invalid_csv_format + message: Invalid CSV format + details: + reason: missing_required_column + column: umaAddress + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + invitation-claimed: + post: + summary: Invitation claimed webhook + description: | + Webhook that is called when an invitation is claimed by a customer. + This endpoint should be implemented by platform clients of the Grid API. + + When a customer claims an invitation, this webhook is triggered to notify the platform that: + 1. The invitation has been successfully claimed + 2. The invitee UMA address is now associated with the invitation + 3. The invitation status has changed from PENDING to CLAIMED + + This allows platforms to: + - Track invitation usage and conversion rates + - Trigger onboarding flows for new customers who joined via invitation + - Apply referral bonuses or rewards to the inviter + - Update their UI to reflect the claimed status + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: invitationClaimedWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InvitationClaimedWebhook' + examples: + claimedInvitation: + summary: Invitation claimed notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: + code: 019542f5 + createdAt: '2025-09-01T14:30:00Z' + claimedAt: '2025-09-01T15:45:00Z' + inviterUma: $inviter@uma.domain + inviteeUma: $invitee@uma.domain + status: CLAIMED + url: https://uma.me/i/019542f5 + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + customer-update: + post: + summary: Customer status change + description: | + Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: customerStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CustomerWebhook' + examples: + kycApprovedWebhook: + summary: When an individual customer KYC has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: Seattle + state: WA + postalCode: '98101' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When an individual customer KYC has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybApprovedWebhook: + summary: When a business customer KYB has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: CUSTOMER.KYB_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000003 + platformCustomerId: 7a2f9d4e1b8c3f5 + customerType: BUSINESS + region: US + currencies: + - USD + - USDC + umaAddress: $acme.corp@uma.domain.com + kybStatus: APPROVED + address: + line1: 456 Business Ave + city: Seattle + state: WA + postalCode: '98101' + country: US + businessInfo: + legalName: Acme Corporation + registrationNumber: '12345678' + taxId: 98-7654321 + incorporatedOn: '2018-03-14' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybRejectedWebhook: + summary: When a business customer KYB has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000010 + type: CUSTOMER.KYB_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000004 + platformCustomerId: 3c8e5f2a9d1b7e4 + customerType: BUSINESS + region: US + currencies: + - USD + umaAddress: $globex.inc@uma.domain.com + kybStatus: REJECTED + address: + line1: 789 Corporate Blvd + city: Chicago + state: IL + postalCode: '60601' + country: US + businessInfo: + legalName: Globex Inc + taxId: 47-1234567 + incorporatedOn: '2015-09-01' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + internal-account-status: + post: + summary: Internal account status webhook + description: | + Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Event types + - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. + - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. + operationId: internalAccountStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccountStatusWebhook' + examples: + balanceDecrease: + summary: A transaction just cleared a customer account and the balance has decreased + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INTERNAL_ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: ACTIVE + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12500 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + statusUpdated: + summary: The status of an internal account changed (e.g., frozen by Grid) + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INTERNAL_ACCOUNT.STATUS_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: FROZEN + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + verification-update: + post: + summary: Verification status change + description: | + Webhook that is called when a customer's KYC/KYB verification status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: verificationStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationWebhook' + examples: + approved: + summary: Verification approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: VERIFICATION.APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: APPROVED + errors: [] + createdAt: '2025-08-15T14:00:00Z' + resolveErrors: + summary: Verification requires action + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000031 + type: VERIFICATION.RESOLVE_ERRORS + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000011 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + createdAt: '2025-08-15T14:00:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-state-change: + post: + summary: Card state change + description: | + Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardStateChangeWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardStateChangeWebhook' + examples: + activated: + summary: Card transitioned from PROCESSING to ACTIVE + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + processorRef: card_b81c2a4f + issuerRef: lead_card_7a1b9c3d + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:11:00Z' + issuerRejected: + summary: Card rejected by issuer during provisioning + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000021 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:12:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000011 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: CLOSED + stateReason: ISSUER_REJECTED + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:12:00Z' + frozen: + summary: Card frozen by the platform + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000022 + type: CARD.STATE_CHANGE + timestamp: '2026-05-09T09:00:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: FROZEN + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-09T09:00:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-funding-source-change: + post: + summary: Card funding source change + description: | + Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardFundingSourceChangeWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardFundingSourceChangeWebhook' + examples: + fundingSourcesReplaced: + summary: Funding sources replaced via PATCH /cards/{id} + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: CARD.FUNDING_SOURCE_CHANGE + timestamp: '2026-05-08T14:30:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:30:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-transaction: + post: + summary: Card transaction + description: | + Webhook that is called on every state transition of a card `CardTransaction`. Fires when an authorization is approved (`CARD_TRANSACTION.AUTHORIZED`), as clearings settle against it (`CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`), when settled funds are returned (`CARD_TRANSACTION.REFUNDED`), and when a pull or confirmation fails (`CARD_TRANSACTION.EXCEPTION`). The payload carries the full `CardTransaction` resource. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardTransactionWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardTransactionWebhook' + examples: + authorized: + summary: Authorization approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: CARD_TRANSACTION.AUTHORIZED + timestamp: '2026-05-09T10:00:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: AUTHORIZED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 1 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 0 + totalAmount: 0 + authorizedAt: '2026-05-09T10:00:00Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-09T10:00:00Z' + settled: + summary: All clearings posted + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: CARD_TRANSACTION.SETTLED + timestamp: '2026-05-09T15:42:11Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: SETTLED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 0 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 1 + totalAmount: 12550 + authorizedAt: '2026-05-09T10:00:00Z' + lastEventAt: '2026-05-09T15:42:11Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-09T15:42:11Z' + refunded: + summary: Settled funds returned by the merchant + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000042 + type: CARD_TRANSACTION.REFUNDED + timestamp: '2026-05-10T09:15:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: REFUNDED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + refundedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 0 + refundSummary: + count: 1 + totalAmount: 12550 + settlementSummary: + count: 1 + totalAmount: 12550 + authorizedAt: '2026-05-09T10:00:00Z' + lastEventAt: '2026-05-10T09:15:00Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-10T09:15:00Z' + exception: + summary: Settled to the network but the funding-source pull failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000043 + type: CARD_TRANSACTION.EXCEPTION + timestamp: '2026-05-09T16:05:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000101 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_c92d3b5a + status: EXCEPTION + direction: DEBIT + merchant: + descriptor: UBER EATS + mcc: '5812' + country: US + authorizedAmount: + amount: 4200 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 4200 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 0 + pendingCount: 0 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 1 + totalAmount: 4200 + authorizedAt: '2026-05-09T15:30:00Z' + lastEventAt: '2026-05-09T16:05:00Z' + createdAt: '2026-05-09T15:30:00Z' + updatedAt: '2026-05-09T16:05:00Z' + responses: + '200': + description: | Webhook received successfully '400': description: Bad request @@ -9439,10 +11394,149 @@ components: example: support@acme.com logoUrl: type: string - format: uri - maxLength: 512 - description: URL to a PNG logo for the OTP email. Resized to 340x124px. - example: https://acme.com/logo.png + format: uri + maxLength: 512 + description: URL to a PNG logo for the OTP email. Resized to 340x124px. + example: https://acme.com/logo.png + CardTokenization2FAEmailConfig: + type: object + description: Email branding and sender settings for card-tokenization authentication messages. Invalid or unverified sender identities can cause delivery to fail. + properties: + fromAddress: + type: string + format: email + maxLength: 255 + description: Sender address for card-tokenization authentication emails. + example: cards@acme.com + fromName: + type: string + maxLength: 255 + description: Sender display name. + example: Acme Cards + replyToAddress: + type: string + format: email + maxLength: 255 + description: Reply-to address for card-tokenization authentication emails. + example: support@acme.com + subject: + type: string + maxLength: 255 + description: Subject for the authentication email. + example: Your Acme card verification code + bodyText: + type: string + maxLength: 2000 + description: | + Plain-text message content. Lightspark inserts the authentication code into + a controlled text and HTML template; arbitrary HTML and template variables + are not supported. + example: Use this code to finish adding your Acme card to your digital wallet. + CardTokenization2FASmsConfig: + type: object + description: SMS settings for card-tokenization authentication messages delivered through a Lightspark-managed Twilio sender. + properties: + templateSid: + type: string + maxLength: 64 + description: | + Twilio Verify template SID to use for this platform. An invalid or unavailable + template can cause delivery to fail. + example: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bodyText: + type: string + maxLength: 480 + description: | + Plain-text fallback message used when Twilio Verify is unavailable for the + authentication code. Lightspark appends the code to this text. + example: Use this code to finish adding your Acme card to your digital wallet. + CardTokenization2FAConfig: + type: object + description: Per-platform branding for authentication codes sent when a cardholder adds a Grid-issued card to a digital wallet. Updates apply to subsequent delivery attempts. Invalid sender or provider configuration can cause delivery to fail. + properties: + displayName: + type: string + maxLength: 255 + description: Platform name displayed in authentication messages. + example: Acme + logoUrl: + type: string + format: uri + pattern: ^https:// + maxLength: 512 + description: HTTPS URL of the logo displayed in email messages. + example: https://acme.com/card-email-logo.png + email: + $ref: '#/components/schemas/CardTokenization2FAEmailConfig' + sms: + $ref: '#/components/schemas/CardTokenization2FASmsConfig' + FeeType: + type: string + enum: + - CROSS_CURRENCY_TRANSACTION + description: | + The kind of activity this fee applies to. + + - `CROSS_CURRENCY_TRANSACTION` — fee charged on a cross-currency Grid transaction + (source currency differs from destination currency). + example: CROSS_CURRENCY_TRANSACTION + Currency: + type: object + properties: + code: + type: string + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: + type: string + description: Full name of the currency + example: United States Dollar + symbol: + type: string + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + CurrencyAmount: + type: object + required: + - amount + - currency + properties: + amount: + type: integer + format: int64 + description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) + example: 12550 + currency: + $ref: '#/components/schemas/Currency' + FeeConfig: + type: object + description: A platform-configured fee collected by Grid and settled to the platform internal account. There can be at most one fee config for a given fee type and source currency pair. The fee will apply to all transactions of the fee type that originate in the source currency. + properties: + feeType: + $ref: '#/components/schemas/FeeType' + sourceCurrency: + type: string + description: Currency code of the sending side this fee applies to. Only `USD` is accepted today; other currencies return a `NOT_IMPLEMENTED` error. + example: USD + variableFeeBps: + type: integer + minimum: 0 + maximum: 10000 + description: Variable fee in basis points (1 bps = 0.01%) to apply to a transaction's source-currency amount. + example: 30 + fixedFee: + $ref: '#/components/schemas/CurrencyAmount' + description: Fixed fee charged per transaction, in the smallest unit of the source currency. The fixed fee currency must match the fee config's `sourceCurrency`. + required: + - feeType + - sourceCurrency + - variableFeeBps + - fixedFee PlatformConfig: type: object properties: @@ -9484,6 +11578,19 @@ components: Embedded-wallet branding and OTP settings for this platform. Present only when the platform has configured embedded-wallet support; omitted otherwise. + cardTokenization2faConfig: + $ref: '#/components/schemas/CardTokenization2FAConfig' + description: | + Branding and sender configuration for card-tokenization authentication + messages. This configuration is independent of embedded-wallet support. + feeConfigs: + type: array + items: + $ref: '#/components/schemas/FeeConfig' + description: | + Platform-collected fees that should be added on top of + Grid-collected fees. Contains every currently-active fee config + for the platform. createdAt: type: string format: date-time @@ -9584,6 +11691,66 @@ components: Fields omitted from the nested object are left unchanged. Omit this field at the top level to leave the embedded-wallet configuration unchanged entirely. + cardTokenization2faConfig: + $ref: '#/components/schemas/CardTokenization2FAConfig' + description: | + Update card-tokenization authentication branding and delivery settings. + Fields omitted from the nested object are left unchanged. Changes apply + to subsequent delivery attempts. + feeConfigs: + type: array + items: + $ref: '#/components/schemas/FeeConfig' + description: | + Merge-by-key upsert of platform fee configs, keyed by + `(feeType, sourceCurrency)`. Setting variable and fixed fees to 0 for + an existing fee config deactivates it. Only `sourceCurrency: USD` is + accepted today. Omit this field to leave fee configs unchanged. + FieldConstraint: + type: object + description: Machine-readable validator hint accompanying a 400 `INVALID_INPUT` error. Consumers use it to drive form UI (input types, dropdowns, masking, length limits) and to pre-validate the field client-side before re-submitting. Fields are additive. + properties: + format: + type: string + description: Named format the value must satisfy — HTML5 input type names (`email`, `tel`, `url`, `date`, ...) or semantic slugs (`iso3166-1-alpha-2`, `bcp47-language-tag`, `us-ssn`, `e.164`). + example: email + pattern: + type: string + description: Regular expression the value must match (JavaScript-flavor). + example: ^\d{5}(-\d{4})?$ + enum: + type: array + items: + type: string + description: Allowed values when the field is drawn from a fixed set. + example: + - SSN + - ITIN + - NON_US_TAX_ID + minLength: + type: integer + description: Minimum length in characters. + example: 1 + maxLength: + type: integer + description: Maximum length in characters. + example: 500 + FieldError: + type: object + required: + - field + description: One field-level validation failure. Field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`) emit an array of these under `details.errors` so platforms can render form-field-level UX for every failure in a single round-trip. + properties: + field: + type: string + description: Dot-notation path to the offending field. + example: taxIdentifier + constraint: + $ref: '#/components/schemas/FieldConstraint' + message: + type: string + description: Human-readable explanation of what's wrong with this field. + example: Value is not one of the allowed enum members. Error400: type: object required: @@ -9685,7 +11852,13 @@ components: description: Error message details: type: object - description: Additional error details + description: Additional error details. Shape varies by `code`. For field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`), `details.errors[]` enumerates every invalid field so platforms can render form-field-level UX for the entire request in a single round-trip. + properties: + errors: + type: array + description: One entry per invalid field. Present on field-validation errors from submit endpoints. + items: + $ref: '#/components/schemas/FieldError' additionalProperties: true Error501: type: object @@ -9716,26 +11889,6 @@ components: type: object description: Additional error details additionalProperties: true - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 PaymentRail: type: string enum: @@ -10011,32 +12164,195 @@ components: type: string description: Country code (ISO 3166-1 alpha-2) example: US - IndividualCustomerFields: + IdentificationType: + type: string + enum: + - SSN + - ITIN + - EIN + - NON_US_TAX_ID + description: Type of tax identification + example: SSN + IndividualSourceOfFundsCategory: + type: string + enum: + - SALARY + - SELF_EMPLOYMENT_INCOME + - INVESTMENT_INCOME + - PENSION + - RENTAL_INCOME + - GIFT + - INHERITANCE + - LOAN + - SAVINGS + - SALE_OF_ASSETS + - OTHER + description: A structured source-of-funds category for an individual customer (FLOW of funds into this account). Distinct from `SourceOfFundsCategory`, which covers business customers. + example: SALARY + SourceOfWealthCategory: + type: string + enum: + - SALARY + - BUSINESS_INCOME + - INVESTMENTS + - INHERITANCE + - PROPERTY_SALE + - GIFT + - RETIREMENT + - SAVINGS + - OTHER + description: Origin of the customer's accumulated wealth (STOCK). Distinct from `SourceOfFundsCategory` which describes the FLOW of funds for this specific account. + example: SALARY + PurposeOfAccount: + type: string + enum: + - CONTRACTOR_PAYOUTS + - CREATOR_PAYOUTS + - EMPLOYEE_PAYOUTS + - MARKETPLACE_SELLER_PAYOUTS + - SUPPLIER_PAYMENTS + - CROSS_BORDER_B2B + - AR_AUTOMATION + - AP_AUTOMATION + - EMBEDDED_PAYMENTS + - PLATFORM_FEE_COLLECTION + - P2P_TRANSFERS + - CHARITABLE_DONATIONS + - OTHER + description: The intended purpose for using the Grid account + example: CONTRACTOR_PAYOUTS + ExpectedMonthlyTransactionCount: + type: string + enum: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + ExpectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + AnnualIncomeRange: + type: string + enum: + - UNDER_50K + - RANGE_50K_100K + - RANGE_100K_250K + - RANGE_250K_1M + - OVER_1M + description: Bucketed annual income (USD equivalent). Used for enhanced due diligence on higher-risk profiles. + example: RANGE_100K_250K + NetWorthRange: + type: string + enum: + - UNDER_100K + - RANGE_100K_500K + - RANGE_500K_1M + - RANGE_1M_5M + - RANGE_5M_25M + - OVER_25M + description: Bucketed total net worth (USD equivalent). Used for enhanced due diligence on higher-risk profiles. + example: RANGE_500K_1M + PepStatus: + type: string + enum: + - NONE + - DOMESTIC + - FOREIGN + - HIO + - FAMILY_OR_ASSOCIATE + description: Political exposure declaration (Politically Exposed Person status). `HIO` = head of an international organization. `FAMILY_OR_ASSOCIATE` covers close family members and known close associates of a PEP. + example: NONE + CustomerEdd: type: object - required: - - customerType + description: Enhanced-due-diligence (EDD) fields available as optional patchable attributes on an individual customer. Referenced via `allOf` from `IndividualCustomerFields`, so these appear as top-level optional fields on the customer resource itself; there is no separate EDD resource. The specific set required for a given customer is driven by the KYC provider's per-jurisdiction / per-flow / per-volume-tier rules (surfaced through `MISSING_FIELD` errors on `POST /verifications`). properties: - customerType: - type: string - enum: - - INDIVIDUAL - kycStatus: - $ref: '#/components/schemas/KycStatus' - fullName: + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/IndividualSourceOfFundsCategory' + description: Structured source-of-funds categories (FLOW of funds for this account). + example: + - SALARY + sourceOfFundsOtherDescription: type: string - description: Individual's full name - example: John Michael Doe - birthDate: + description: Free-form description of the customer's source of funds. Required when `sourceOfFundsCategories` includes `OTHER`; otherwise omitted. + example: Contest winnings + sourceOfWealthCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfWealthCategory' + description: Structured source-of-wealth categories (STOCK — origin of accumulated wealth). + example: + - SALARY + - INVESTMENTS + sourceOfWealthOtherDescription: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1990-01-15' - nationality: + description: Free-form description of the customer's source of wealth. Required when `sourceOfWealthCategories` includes `OTHER`; otherwise omitted. + example: Royalty income from published works + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + purposeOfAccountOtherDescription: type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - address: - $ref: '#/components/schemas/Address' + description: Free-form description of the customer's intended purpose for the Grid account. Required when `purposeOfAccount` is `OTHER`; otherwise omitted. + example: Household budgeting between spouses + expectedMonthlyTransactionCount: + $ref: '#/components/schemas/ExpectedMonthlyTransactionCount' + expectedMonthlyTransactionVolume: + $ref: '#/components/schemas/ExpectedMonthlyTransactionVolume' + annualIncomeRange: + $ref: '#/components/schemas/AnnualIncomeRange' + netWorthRange: + $ref: '#/components/schemas/NetWorthRange' + pepStatus: + $ref: '#/components/schemas/PepStatus' + IndividualCustomerFields: + allOf: + - type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - INDIVIDUAL + kycStatus: + $ref: '#/components/schemas/KycStatus' + fullName: + type: string + description: Individual's full name + example: John Michael Doe + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1990-01-15' + nationality: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + address: + $ref: '#/components/schemas/Address' + taxIdType: + $ref: '#/components/schemas/IdentificationType' + taxIdentifier: + type: string + description: Tax-identification number. For US persons this is the SSN (format `###-##-####`) or ITIN. For non-US persons this is the tax number issued by `taxIdCountryOfIssuance`. + example: 123-45-6789 + taxIdCountryOfIssuance: + type: string + description: Country that issued the tax identifier (ISO 3166-1 alpha-2). Required when `taxIdType` is `NON_US_TAX_ID`. + example: US + - $ref: '#/components/schemas/CustomerEdd' IndividualCustomer: title: Individual Customer allOf: @@ -10061,6 +12377,10 @@ components: - CORPORATION - S_CORPORATION - NON_PROFIT + - PUBLICLY_LISTED_COMPANY + - TRUST + - PRIVATE_FOUNDATION + - CHARITY - OTHER description: Legal entity type of the business example: LLC @@ -10088,24 +12408,6 @@ components: - ACCOMMODATION_AND_FOOD_SERVICES - OTHER_SERVICES - PUBLIC_ADMINISTRATION - PurposeOfAccount: - type: string - enum: - - CONTRACTOR_PAYOUTS - - CREATOR_PAYOUTS - - EMPLOYEE_PAYOUTS - - MARKETPLACE_SELLER_PAYOUTS - - SUPPLIER_PAYMENTS - - CROSS_BORDER_B2B - - AR_AUTOMATION - - AP_AUTOMATION - - EMBEDDED_PAYMENTS - - PLATFORM_FEE_COLLECTION - - P2P_TRANSFERS - - CHARITABLE_DONATIONS - - OTHER - description: The intended purpose for using the Grid account - example: CONTRACTOR_PAYOUTS SourceOfFundsCategory: type: string enum: @@ -10120,7 +12422,131 @@ components: example: OPERATING_REVENUE BusinessInfoUpdate: type: object - description: Additional information for business entities + description: Additional information for business entities + properties: + legalName: + type: string + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: + type: string + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: + type: string + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: + type: string + description: Business registration number + example: '5523041' + incorporatedOn: + type: string + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: + type: string + description: Tax identification number + example: 47-1234567 + countriesOfOperation: + type: array + items: + type: string + description: List of countries where the business operates (ISO 3166-1 alpha-2) + example: + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: + type: string + enum: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BusinessCustomerFields: + type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - BUSINESS + kybStatus: + $ref: '#/components/schemas/KybStatus' + address: + $ref: '#/components/schemas/Address' + businessInfo: + $ref: '#/components/schemas/BusinessInfoUpdate' + BusinessInfoResponse: + type: object + description: | + Business information returned on a customer. `taxId` and `incorporatedOn` are + required on creation but may be absent on legacy customers that pre-date the + requirement, so both are optional in responses. + required: + - legalName properties: legalName: type: string @@ -10167,84 +12593,300 @@ components: expectedMonthlyTransactionCount: type: string enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BeneficialOwnerRole: + type: string + enum: + - UBO + - DIRECTOR + - COMPANY_OFFICER + - CONTROL_PERSON + - TRUSTEE + - GENERAL_PARTNER + description: Role of the beneficial owner within the business + example: UBO + BeneficialOwnerPersonalInfo: + type: object + required: + - firstName + - lastName + - birthDate + - nationality + - address + - idType + - identifier + properties: + firstName: + type: string + description: First name of the individual + example: Jane + middleName: + type: string + description: Middle name of the individual + example: Marie + lastName: + type: string + description: Last name of the individual + example: Smith + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1978-06-15' + nationality: + type: string + description: Country of nationality (ISO 3166-1 alpha-2) + example: US + email: + type: string + format: email + description: Email address of the individual + example: jane.smith@acmecorp.com + phoneNumber: + type: string + description: Phone number in E.164 format + example: '+14155550192' + pattern: ^\+[1-9]\d{1,14}$ + address: + $ref: '#/components/schemas/Address' + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + description: The identification number or value + example: 123-45-6789 + countryOfIssuance: + type: string + description: Country that issued the identification (ISO 3166-1 alpha-2) + example: US + BeneficialOwner: + type: object + required: + - id + - customerId + - roles + - ownershipPercentage + - personalInfo + - kycStatus + - createdAt + properties: + id: + type: string + description: Unique identifier for this beneficial owner + example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 + customerId: + type: string + description: The ID of the business customer this beneficial owner is associated with + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + roles: + type: array + items: + $ref: '#/components/schemas/BeneficialOwnerRole' + description: Roles of this person within the business + example: + - UBO + - DIRECTOR + ownershipPercentage: + type: integer + description: Percentage of ownership in the business (0-100) + minimum: 0 + maximum: 100 + example: 51 + personalInfo: + $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' + kycStatus: + $ref: '#/components/schemas/KycStatus' + createdAt: + type: string + format: date-time + description: When this beneficial owner was created + example: '2025-10-03T12:00:00Z' + updatedAt: + type: string + format: date-time + description: When this beneficial owner was last updated + example: '2025-10-03T12:00:00Z' + BusinessCustomer: + title: Business Customer + allOf: + - $ref: '#/components/schemas/Customer' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfoResponse' + beneficialOwners: + type: array + items: + $ref: '#/components/schemas/BeneficialOwner' + CustomerOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomer' + - $ref: '#/components/schemas/BusinessCustomer' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomer' + BUSINESS: '#/components/schemas/BusinessCustomer' + CustomerListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + description: List of customers matching the filter criteria + items: + $ref: '#/components/schemas/CustomerOneOf' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: + type: string + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of customers matching the criteria (excluding pagination) + Error405: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 405 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + - METHOD_NOT_ALLOWED + message: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerCreateRequest: + type: object + required: + - customerType + properties: + platformCustomerId: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + description: Platform-specific customer identifier. If not provided, one will be generated by the system. + example: 9f84e0c2a72c4fa + customerType: + $ref: '#/components/schemas/CustomerType' + region: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + example: US + currencies: type: array items: type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. example: - - US - BusinessCustomerFields: - type: object - required: - - customerType - properties: - customerType: + - USD + - USDC + email: type: string - enum: - - BUSINESS - kybStatus: - $ref: '#/components/schemas/KybStatus' - address: - $ref: '#/components/schemas/Address' - businessInfo: - $ref: '#/components/schemas/BusinessInfoUpdate' - BusinessInfoResponse: + format: email + description: Email address for the customer. **Required in regions that verify the email address before identity verification** (e.g. the EU); optional otherwise. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. **Required in regions that verify the phone number before identity verification** (e.g. the EU); optional otherwise. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerCreateRequest: + title: Individual Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + - type: object + properties: + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + writeOnly: true + description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. + example: 123-45-6789 + BusinessInfo: type: object - description: | - Business information returned on a customer. `taxId` and `incorporatedOn` are - required on creation but may be absent on legacy customers that pre-date the - requirement, so both are optional in responses. + description: Additional information required for business entities required: - legalName + - taxId + - incorporatedOn properties: legalName: type: string @@ -10346,454 +12988,641 @@ components: description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) example: - US - BeneficialOwnerRole: - type: string - enum: - - UBO - - DIRECTOR - - COMPANY_OFFICER - - CONTROL_PERSON - - TRUSTEE - - GENERAL_PARTNER - description: Role of the beneficial owner within the business - example: UBO - IdentificationType: - type: string - enum: - - SSN - - ITIN - - EIN - - NON_US_TAX_ID - description: Type of tax identification - example: SSN - BeneficialOwnerPersonalInfo: + BusinessCustomerCreateRequest: + title: Business Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + required: + - businessInfo + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfo' + CustomerCreateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' + Error409: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 409 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | + | TRANSACTION_NOT_CANCELLABLE | Transaction has already settled or is otherwise past the point where it can be cancelled | + | UMA_ADDRESS_EXISTS | UMA address already exists | + | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | + | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | + | PASSKEY_ALREADY_ENROLLED | The customer already has an enrolled passkey factor; only one passkey per customer is supported. Delete the existing one before enrolling another | + | SCA_SESSION_REQUIRED | The customer's Strong Customer Authentication login session is missing or expired. Re-authenticate the customer, then retry the request. Distinct from a `401`, which means the platform's own API credentials were rejected | + | BENEFICIARY_TRUSTED | The external account is currently a trusted beneficiary, so it cannot be deleted. Untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete | + | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | + enum: + - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL + - TRANSACTION_NOT_CANCELLABLE + - UMA_ADDRESS_EXISTS + - EMAIL_OTP_EMAIL_ALREADY_EXISTS + - EMAIL_OTP_CREDENTIAL_SET_CHANGED + - PASSKEY_ALREADY_ENROLLED + - SCA_SESSION_REQUIRED + - BENEFICIARY_TRUSTED + - CONFLICT + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error404: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 404 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_FOUND | Transaction not found | + | INVITATION_NOT_FOUND | Invitation not found | + | USER_NOT_FOUND | Customer not found | + | QUOTE_NOT_FOUND | Quote not found | + | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | + | TOKEN_NOT_FOUND | Token not found | + | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | + | REFERENCE_NOT_FOUND | Reference not found | + | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | + | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | + enum: + - TRANSACTION_NOT_FOUND + - INVITATION_NOT_FOUND + - USER_NOT_FOUND + - QUOTE_NOT_FOUND + - LOOKUP_REQUEST_NOT_FOUND + - TOKEN_NOT_FOUND + - BULK_UPLOAD_JOB_NOT_FOUND + - REFERENCE_NOT_FOUND + - UMA_NOT_FOUND + - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error410: type: object required: - - firstName - - lastName - - birthDate - - nationality - - address - - idType - - identifier + - message + - status + - code properties: - firstName: - type: string - description: First name of the individual - example: Jane - middleName: - type: string - description: Middle name of the individual - example: Marie - lastName: - type: string - description: Last name of the individual - example: Smith - birthDate: + status: + type: integer + enum: + - 410 + description: HTTP status code + code: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1978-06-15' - nationality: + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | + enum: + - CUSTOMER_DELETED + message: type: string - description: Country of nationality (ISO 3166-1 alpha-2) - example: US + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerUpdateRequest: + title: Customer Update Request + description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. + type: object + required: + - customerType + properties: + customerType: + $ref: '#/components/schemas/CustomerType' + currencies: + type: array + items: + type: string + description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + example: + - USD + - EUR + - USDC email: type: string format: email - description: Email address of the individual - example: jane.smith@acmecorp.com + description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. + example: john.doe@example.com phoneNumber: type: string - description: Phone number in E.164 format - example: '+14155550192' pattern: ^\+[1-9]\d{1,14}$ - address: - $ref: '#/components/schemas/Address' - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - description: The identification number or value - example: 123-45-6789 - countryOfIssuance: + description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. + example: '+14155551234' + umaAddress: type: string - description: Country that issued the identification (ISO 3166-1 alpha-2) - example: US - BeneficialOwner: + description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerUpdateRequest: + title: Individual Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + BusinessCustomerUpdateRequest: + title: Business Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + CustomerUpdateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + SignedRequestChallenge: + title: Signed Request Challenge type: object required: - - id - - customerId - - roles - - ownershipPercentage - - personalInfo - - kycStatus - - createdAt + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. properties: - id: + payloadToSign: type: string - description: Unique identifier for this beneficial owner - example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 - customerId: + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' + requestId: type: string - description: The ID of the business customer this beneficial owner is associated with - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - roles: - type: array - items: - $ref: '#/components/schemas/BeneficialOwnerRole' - description: Roles of this person within the business - example: - - UBO - - DIRECTOR - ownershipPercentage: - type: integer - description: Percentage of ownership in the business (0-100) - minimum: 0 - maximum: 100 - example: 51 - personalInfo: - $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' - kycStatus: - $ref: '#/components/schemas/KycStatus' - createdAt: + description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: type: string format: date-time - description: When this beneficial owner was created - example: '2025-10-03T12:00:00Z' - updatedAt: + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' + Error424: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 424 + description: HTTP status code + code: type: string - format: date-time - description: When this beneficial owner was last updated - example: '2025-10-03T12:00:00Z' - BusinessCustomer: - title: Business Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfoResponse' - beneficialOwners: - type: array - items: - $ref: '#/components/schemas/BeneficialOwner' - CustomerOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomer' - - $ref: '#/components/schemas/BusinessCustomer' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomer' - BUSINESS: '#/components/schemas/BusinessCustomer' - CustomerListResponse: + description: | + | Error Code | Description | + |------------|-------------| + | PAYREQ_REQUEST_FAILED | Payment request failed | + | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | + | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | + | LNURLP_REQUEST_FAILED | LNURLP request failed | + | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | + enum: + - PAYREQ_REQUEST_FAILED + - COUNTERPARTY_PUBKEY_FETCH_ERROR + - NO_COMPATIBLE_UMA_VERSION + - LNURLP_REQUEST_FAILED + - EMAIL_OTP_CREDENTIAL_SYNC_FAILED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + KycLinkCreateRequest: + type: object + description: Request body for generating a hosted KYC link for an existing customer. + properties: + redirectUri: + type: string + format: uri + description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. + example: https://app.example.com/onboarding/completed + KycProvider: + type: string + description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. + enum: + - SUMSUB + example: SUMSUB + KycLinkResponse: type: object + description: A hosted KYC link that the customer can complete to verify their identity. required: - - data - - hasMore + - kycUrl + - expiresAt + - provider properties: - data: - type: array - description: List of customers matching the filter criteria - items: - $ref: '#/components/schemas/CustomerOneOf' - hasMore: - type: boolean - description: Indicates if more results are available beyond this page - nextCursor: + kycUrl: type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) - totalCount: - type: integer - description: Total number of customers matching the criteria (excluding pagination) - Error405: + description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. + example: https://kyc.lightspark.com/onboard/abc123def456 + expiresAt: + type: string + format: date-time + description: Time at which the hosted link expires and can no longer be used. + example: '2027-01-15T14:32:00Z' + provider: + $ref: '#/components/schemas/KycProvider' + token: + type: string + description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. + example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ContactVerificationConfirmRequest: type: object + description: Request body for confirming an email or phone verification challenge. required: - - message - - status - code properties: - status: - type: integer - enum: - - 405 - description: HTTP status code code: type: string - description: | - | Error Code | Description | - |------------|-------------| - | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | - enum: - - METHOD_NOT_ALLOWED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerCreateRequest: + description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. + example: '123456' + ScaFactor: + type: string + enum: + - SMS_OTP + - TOTP + - PASSKEY + description: | + A Strong Customer Authentication factor. + + | Factor | Description | + |--------|-------------| + | `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. | + | `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). | + | `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. | + ScaFactorView: type: object + description: An enrolled Strong Customer Authentication factor. required: - - customerType + - factor properties: - platformCustomerId: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The kind of enrolled factor. + credentialId: type: string - description: Platform-specific customer identifier. If not provided, one will be generated by the system. - example: 9f84e0c2a72c4fa - customerType: - $ref: '#/components/schemas/CustomerType' - region: + description: The per-credential id, populated only for `PASSKEY` factors (the id passed to delete a passkey). Omitted for `TOTP` and `SMS_OTP`, which have no per-credential id. + name: type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. - example: US - currencies: + description: An optional human-readable label for this factor. + ScaFactorList: + type: object + description: The Strong Customer Authentication factors a customer has enrolled. + required: + - factors + properties: + factors: type: array + description: The customer's enrolled SCA factors. items: - type: string - description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. - example: - - USD - - USDC - email: - type: string - format: email - description: Email address for the customer. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. - example: '+14155551234' - umaAddress: + $ref: '#/components/schemas/ScaFactorView' + TotpFactorEnrollRequest: + type: object + title: TOTP Factor Enroll Request + description: Start enrolling a time-based one-time-password (TOTP) authenticator factor. + required: + - type + properties: + type: type: string - description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerCreateRequest: - title: Individual Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - - type: object - properties: - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - writeOnly: true - description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. - example: 123-45-6789 - BusinessInfo: + enum: + - TOTP + description: Discriminator selecting the TOTP factor. TOTP enrollment needs no other input at start. + PasskeyFactorEnrollRequest: type: object - description: Additional information required for business entities + title: Passkey Factor Enroll Request + description: Start enrolling a WebAuthn passkey factor. required: - - legalName - - taxId - - incorporatedOn + - type properties: - legalName: + type: type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: + enum: + - PASSKEY + description: Discriminator selecting the passkey factor. Passkey enrollment needs no other input at start. + ScaFactorEnrollRequestOneOf: + oneOf: + - $ref: '#/components/schemas/TotpFactorEnrollRequest' + - $ref: '#/components/schemas/PasskeyFactorEnrollRequest' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpFactorEnrollRequest' + PASSKEY: '#/components/schemas/PasskeyFactorEnrollRequest' + description: Which SCA factor to begin enrolling, selected by `type`. `SMS_OTP` is not enrollable (it uses the customer's verified phone), so only `TOTP` and `PASSKEY` are valid here. + TotpEnrollmentStart: + type: object + description: The shared secret a customer's authenticator app needs to enroll a TOTP factor. Returned by `POST /sca/factors` for a `TOTP` request; the customer scans `totpUri` (an `otpauth://` provisioning URI) and confirms with the first code their app produces. + required: + - type + - secret + - secretBase32Encoded + - totpUri + properties: + type: type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: + enum: + - TOTP + description: Discriminator identifying this as the TOTP enrollment-start payload. + secret: type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: + description: The raw TOTP shared secret. + secretBase32Encoded: type: string - description: Business registration number - example: '5523041' - incorporatedOn: + description: The Base32-encoded shared secret, suitable for manual entry into an authenticator app that does not scan QR codes. + totpUri: type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: + description: The `otpauth://` provisioning URI (the QR-code payload) the customer's authenticator app scans to enroll the factor. + example: otpauth://totp/Grid:customer@example.com?secret=ABC123&issuer=Grid + PasskeyEnrollmentStart: + type: object + description: Opaque WebAuthn registration options relayed to the end user's device to enroll a passkey factor. Grid performs no crypto; pass `options` to the device's WebAuthn API to produce a credential, then submit that credential to the confirm endpoint unmodified. + required: + - type + - options + - allowedOrigins + - relyingPartyId + properties: + type: type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: + enum: + - PASSKEY + description: Discriminator identifying this as the passkey enrollment-start payload. + options: + type: object + additionalProperties: true + description: Opaque WebAuthn `PublicKeyCredentialCreationOptions`. Pass to the device's WebAuthn registration API unmodified. + allowedOrigins: type: array + description: The origins the WebAuthn registration ceremony may run against. The origin the credential is produced against must be one of these and must be echoed back on the confirm call. items: type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: + - https://app.example.com + relyingPartyId: type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: + description: The WebAuthn relying-party id the credential is bound to. + example: app.example.com + ScaFactorEnrollStartOneOf: + oneOf: + - $ref: '#/components/schemas/TotpEnrollmentStart' + - $ref: '#/components/schemas/PasskeyEnrollmentStart' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpEnrollmentStart' + PASSKEY: '#/components/schemas/PasskeyEnrollmentStart' + description: 'The factor-specific material needed to complete enrollment, keyed by `type`: a TOTP shared secret + provisioning URI, or the WebAuthn registration options for a passkey.' + TotpEnrollmentConfirmRequest: + type: object + description: The shared secret returned by the TOTP enrollment start, plus the first code the customer's authenticator app produces, submitted to confirm and finalize the TOTP factor. + required: + - type + - secret + - code + properties: + type: type: string enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - TOTP + description: Discriminator selecting the TOTP confirm variant. + secret: type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + description: The shared secret returned as `secret` by the TOTP enrollment start, threaded back to bind the confirmation to that enrollment. + code: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: The current time-based one-time code from the customer's authenticator app. In sandbox, the code is always `123456`. + example: '123456' + PasskeyEnrollmentConfirmRequest: + type: object + description: The WebAuthn credential a device produced for a passkey registration challenge, submitted to enroll the passkey factor. + required: + - type + - origin + - credential + properties: + type: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + enum: + - PASSKEY + description: Discriminator selecting the passkey confirm variant. + origin: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: - type: array - items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BusinessCustomerCreateRequest: - title: Business Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfo' - CustomerCreateRequestOneOf: + description: The WebAuthn origin the `credential` was produced against (one of the enrollment start's `allowedOrigins`). + example: https://app.example.com + credential: + type: object + additionalProperties: true + description: Opaque WebAuthn credential the device produced from the enrollment start's `options`. + ScaFactorConfirmRequestOneOf: oneOf: - - $ref: '#/components/schemas/IndividualCustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + - $ref: '#/components/schemas/TotpEnrollmentConfirmRequest' + - $ref: '#/components/schemas/PasskeyEnrollmentConfirmRequest' discriminator: - propertyName: customerType + propertyName: type mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' - Error409: + TOTP: '#/components/schemas/TotpEnrollmentConfirmRequest' + PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmRequest' + description: 'The proof that finalizes enrollment, keyed by `type`: the TOTP shared secret + code, or the passkey `origin` + `credential`.' + TotpEnrollmentConfirmResponse: type: object + description: The one-time recovery codes issued once a TOTP factor is enrolled. These are shown to the customer only once; store them somewhere safe to recover access if the authenticator device is lost. required: - - message - - status - - code + - type + - recoveryCodes properties: - status: - type: integer - enum: - - 409 - description: HTTP status code - code: + type: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | - | UMA_ADDRESS_EXISTS | UMA address already exists | - | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | - | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | - | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | enum: - - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL - - UMA_ADDRESS_EXISTS - - EMAIL_OTP_EMAIL_ALREADY_EXISTS - - EMAIL_OTP_CREDENTIAL_SET_CHANGED - - CONFLICT - message: + - TOTP + description: Discriminator identifying this as the TOTP enrollment result. + recoveryCodes: + type: array + description: The one-time recovery codes for this TOTP factor. + items: + type: string + example: + - ABCD-EFGH-IJKL + - MNOP-QRST-UVWX + PasskeyEnrollmentConfirmResponse: + type: object + description: The enrolled passkey factor returned after a successful confirmation. + required: + - type + - factor + properties: + type: type: string - description: Error message - details: - type: object - description: Additional error details + enum: + - PASSKEY + description: Discriminator identifying this as the passkey enrollment result. + factor: + $ref: '#/components/schemas/ScaFactorView' + ScaFactorConfirmResponseOneOf: + oneOf: + - $ref: '#/components/schemas/TotpEnrollmentConfirmResponse' + - $ref: '#/components/schemas/PasskeyEnrollmentConfirmResponse' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpEnrollmentConfirmResponse' + PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmResponse' + description: The enrollment result, keyed by `type`. + ScaLoginStartRequest: + type: object + description: Selects which enrolled factor to start an SCA login with. The factor must already be enrolled (or, for `SMS_OTP`, the phone verified). + required: + - factor + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor to authenticate with. + ScaLoginStart: + type: object + description: 'The factor-specific material a customer needs to complete an SCA login. Each factor surfaces only the fields it issues: `SMS_OTP` carries `challengeId` and `expiresAt`; `TOTP` carries neither (the customer reads the code from their authenticator app); `PASSKEY` carries the opaque WebAuthn `passkeyOptions` with `allowedOrigins` and `relyingPartyId`.' + required: + - factor + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor this login was started for. + challengeId: + type: + - string + - 'null' + description: The challenge handle for an `SMS_OTP` login, threaded back on the complete call. Present only for `SMS_OTP`. + expiresAt: + type: + - string + - 'null' + format: date-time + description: Absolute UTC timestamp after which the `SMS_OTP` code expires. Present only for `SMS_OTP`. + example: '2025-10-03T12:05:00Z' + passkeyOptions: + type: + - object + - 'null' additionalProperties: true - Error404: + description: Opaque WebAuthn assertion request options. Present only for `PASSKEY`; pass to the device's WebAuthn API to produce the assertion submitted on the complete call. + allowedOrigins: + type: + - array + - 'null' + items: + type: string + description: The origins the WebAuthn ceremony may run against. Present only for `PASSKEY`. + example: + - https://app.example.com + relyingPartyId: + type: + - string + - 'null' + description: The WebAuthn relying-party id. Present only for `PASSKEY`. + example: app.example.com + ScaLoginCompleteRequest: type: object + description: Completes an SCA login by submitting the proof for the started factor. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`), plus the `factor` being completed, the `endUserIpAddress` the login is being performed from, and, for `SMS_OTP`, the `challengeId` returned by the login start. + required: + - factor + - endUserIpAddress + anyOf: + - required: + - code + - required: + - passkeyAssertion + - origin + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor being completed; must match the started login. + endUserIpAddress: + type: string + description: The IP address of the end user's device completing this login, recorded against the login event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and any transaction-risk exemption. + example: 203.0.113.42 + challengeId: + type: + - string + - 'null' + description: The challenge handle returned by the login start, required for `SMS_OTP` and omitted for other factors. + code: + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. + example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the login start's `passkeyOptions`. Required when completing a `PASSKEY` login. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against (one of the login start's `allowedOrigins`). Required alongside `passkeyAssertion`; omit it for the `code` path. + example: https://app.example.com + ScaLoginComplete: + type: object + description: The status of a completed SCA login session. required: - - message - status - - code properties: status: - type: integer - enum: - - 404 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_FOUND | Transaction not found | - | INVITATION_NOT_FOUND | Invitation not found | - | USER_NOT_FOUND | Customer not found | - | QUOTE_NOT_FOUND | Quote not found | - | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | - | TOKEN_NOT_FOUND | Token not found | - | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | - | REFERENCE_NOT_FOUND | Reference not found | - | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | - | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | - enum: - - TRANSACTION_NOT_FOUND - - INVITATION_NOT_FOUND - - USER_NOT_FOUND - - QUOTE_NOT_FOUND - - LOOKUP_REQUEST_NOT_FOUND - - TOKEN_NOT_FOUND - - BULK_UPLOAD_JOB_NOT_FOUND - - REFERENCE_NOT_FOUND - - UMA_NOT_FOUND - - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND - message: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error410: + description: The status of the login session. A successful login reports `SUCCESS`; other values indicate the login did not complete and should be surfaced to the caller. + example: SUCCESS + sessionExpiresAt: + type: + - string + - 'null' + format: date-time + description: Absolute UTC timestamp after which the customer's SCA session is no longer valid and they must complete another SCA login. Money movement in SCA-regulated currencies is refused once it passes, so prompt a re-login ahead of it rather than waiting for a `SCA_SESSION_REQUIRED` failure. Present when the login established a session. + example: '2026-01-29T12:00:00Z' + Error423: type: object required: - message @@ -10803,97 +13632,117 @@ components: status: type: integer enum: - - 410 + - 423 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | CUSTOMER_DELETED | Customer has been permanently deleted | + | ACCOUNT_LOCKED | The customer's login is temporarily locked (or suspended) after too many failed attempts. Inspect `details.lockedUntil` for when the customer may retry, and `details.failedAttempts` for the current count. | enum: - - CUSTOMER_DELETED + - ACCOUNT_LOCKED message: type: string description: Error message details: type: object - description: Additional error details + description: Lockout detail. `lockedUntil` is the UTC timestamp the customer may retry after (absent when suspended indefinitely); `failedAttempts` is the current cumulative failed-login count; `suspended` is true when the account is locked with no automatic expiry. + properties: + suspended: + type: boolean + lockedUntil: + type: + - string + - 'null' + format: date-time + failedAttempts: + type: integer additionalProperties: true - CustomerUpdateRequest: - title: Customer Update Request - description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. + RecordSecurityEventRequest: type: object + description: Records a client-side security event for the customer so Grid can maintain the customer's login-security state (SCA session revocation and failed-login lockout). required: - - customerType + - eventType properties: - customerType: - $ref: '#/components/schemas/CustomerType' - currencies: - type: array - items: - type: string - description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. - example: - - USD - - EUR - - USDC - email: - type: string - format: email - description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. - example: john.doe@example.com - phoneNumber: + eventType: type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. - example: '+14155551234' - umaAddress: + enum: + - RESET_PASSWORD_COMPLETED + - FAILED_LOGIN_ATTEMPT + description: | + The security event to record: + + | Value | Effect | + |-------|--------| + | `RESET_PASSWORD_COMPLETED` | Revokes every active SCA session for the customer and clears the failed-login counter. | + | `FAILED_LOGIN_ATTEMPT` | Increments the cumulative failed-login counter and escalates the lockout at each milestone: 5 attempts → 15 minutes, 6 → 30 minutes, 7 → 1 hour, 8 → 24 hours, 9 or more → suspension. | + example: FAILED_LOGIN_ATTEMPT + RecordSecurityEventResponse: + type: object + description: The customer's login-security state after recording the event, so the integrator can surface a lockout to the end user. + required: + - eventType + - suspended + - failedAttempts + properties: + eventType: type: string - description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerUpdateRequest: - title: Individual Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - BusinessCustomerUpdateRequest: - title: Business Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - CustomerUpdateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - SignedRequestChallenge: - title: Signed Request Challenge + enum: + - RESET_PASSWORD_COMPLETED + - FAILED_LOGIN_ATTEMPT + description: The event that was recorded. + example: FAILED_LOGIN_ATTEMPT + suspended: + type: boolean + description: Whether the customer's login is currently suspended (locked with no automatic expiry). A suspended customer must go through a password reset (`RESET_PASSWORD_COMPLETED`) to clear the lockout. + lockedUntil: + type: + - string + - 'null' + format: date-time + description: When the customer may attempt to log in again, if temporarily locked. Null when not locked, or when `suspended` is true (no automatic expiry). + example: '2025-10-03T12:15:00Z' + failedAttempts: + type: integer + description: The customer's current cumulative failed-login count. + example: 5 + TwoFactorResetStartRequest: type: object + description: Selects which enrolled factor to reset via the liveness-gated recovery flow. required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. + - factor properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' - requestId: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The enrolled factor to reset. + TwoFactorResetStart: + type: object + description: The reset handle plus the opaque liveness handles a caller relays to the end-user device to complete the liveness check. `resetId` threads the ceremony together (status and complete reference it). `livenessAccessToken` and `verificationLink` are omitted when they are not returned. + required: + - resetId + properties: + resetId: type: string - description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + description: Identifier for this reset; pass it to the status and complete endpoints. + livenessAccessToken: + type: + - string + - 'null' + description: Access token for the embedded liveness/verification SDK, bound to this reset. Omitted when one is not returned. + verificationLink: + type: + - string + - 'null' + description: Hosted identity-verification page URL for completing liveness. Omitted when one is not returned. expiresAt: - type: string + type: + - string + - 'null' format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' - Error424: + description: Absolute UTC timestamp at the end of the reset window. Omitted when one is not returned. + example: '2025-10-03T12:30:00Z' + Error429: type: object required: - message @@ -10903,79 +13752,192 @@ components: status: type: integer enum: - - 424 + - 429 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | PAYREQ_REQUEST_FAILED | Payment request failed | - | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | - | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | - | LNURLP_REQUEST_FAILED | LNURLP request failed | - | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | + | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | enum: - - PAYREQ_REQUEST_FAILED - - COUNTERPARTY_PUBKEY_FETCH_ERROR - - NO_COMPATIBLE_UMA_VERSION - - LNURLP_REQUEST_FAILED - - EMAIL_OTP_CREDENTIAL_SYNC_FAILED + - RATE_LIMITED message: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - KycLinkCreateRequest: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + TwoFactorResetStatus: + type: object + description: The status of an in-progress 2FA reset, polled until it reaches a terminal value. + required: + - status + - factor + - expiresAt + properties: + status: + type: string + enum: + - INITIATED + - PENDING_REVIEW + - LIVENESS_PASSED + - COMPLETED + - REJECTED + - EXPIRED + description: |- + The reset status. + | Value | Terminal | Meaning | |-------|----------|---------| | `INITIATED` | no | Reset started; liveness not yet submitted. Keep polling. | | `PENDING_REVIEW` | no | Liveness submitted; under review. Keep polling. | | `LIVENESS_PASSED` | no | Liveness passed; call the complete endpoint to finish the reset. | | `COMPLETED` | **yes** | Reset finished; the lost factor is cleared and re-enrollment can begin. | | `REJECTED` | **yes** | Liveness failed. Stop polling; start a new reset. | | `EXPIRED` | **yes** | The reset window closed before it completed. Stop polling; start a new reset. | + Stop polling on any terminal value. + example: INITIATED + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor being reset. + enrollmentStatus: + type: + - string + - 'null' + enum: + - PENDING + - COMPLETED + - null + description: The enrollment status of the replacement factor. `PENDING` until the customer finishes re-enrolling, then `COMPLETED`. Null for an `SMS_OTP` reset, where completing the reset enrolls the new number directly with no separate enrollment step. + example: PENDING + expiresAt: + type: string + format: date-time + description: When the reset window closes. Poll no longer than this. + example: '2025-10-03T12:15:00Z' + completedAt: + type: + - string + - 'null' + format: date-time + description: When the reset completed; null until then. + example: null + TwoFactorResetCompleteRequest: type: object - description: Request body for generating a hosted KYC link for an existing customer. + description: Optional body for completing a 2FA reset. Only needed when resetting the `SMS_OTP` factor to a new phone number; omit the body entirely otherwise. properties: - redirectUri: - type: string - format: uri - description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. - example: https://app.example.com/onboarding/completed - KycProvider: - type: string - description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. - enum: - - SUMSUB - example: SUMSUB - KycLinkResponse: + mobile: + type: object + description: For an `SMS_OTP` reset, the new mobile number to enroll as the customer completes the reset. Required for an `SMS_OTP` reset; ignored for `TOTP` / `PASSKEY` resets. + required: + - countryCode + - number + properties: + countryCode: + type: string + description: The country dialing code, including the leading `+`. + example: '+1' + number: + type: string + description: The national subscriber number, without the country code. + example: '4155550123' + ScaChallenge: type: object - description: A hosted KYC link that the customer can complete to verify their identity. + description: |- + A Strong Customer Authentication challenge that must be satisfied before a money-movement operation can complete. This object is **only present when the customer is in a region where SCA is required** (the EU); for customers outside SCA-regulated regions it is omitted entirely and no action is needed. + + When present on a quote, authorize it by submitting an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize`. + + **A single operation may require more than one authorization, in sequence.** Treat `scaChallenge` as *the challenge to satisfy now*, not "the only one". After each authorize, re-inspect the returned quote/transaction: if it is still `PENDING_AUTHORIZATION`, it carries the **next** `scaChallenge` (a new `id`) — authorize that too, and repeat until it leaves `PENDING_AUTHORIZATION`. Do not assume one authorization releases the transfer. The number of authorizations is flow-dependent and **may decrease in future**: for example, a cross-currency send today authorizes the currency conversion and the payout as two separate challenges; a future update may collapse them into one. A client written to loop on status handles any count unchanged. required: - - kycUrl + - id - expiresAt - - provider + - factor + - availableFactors properties: - kycUrl: + id: type: string - description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. - example: https://kyc.lightspark.com/onboard/abc123def456 + description: Unique identifier for this challenge. The server resolves the active challenge from the quote or transaction being authorized, so this field need not be supplied back; it is informational (e.g. for logging or correlation). + example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007 expiresAt: type: string format: date-time - description: Time at which the hosted link expires and can no longer be used. - example: '2027-01-15T14:32:00Z' - provider: - $ref: '#/components/schemas/KycProvider' - token: - type: string - description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. - example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ContactVerificationConfirmRequest: + description: Absolute UTC timestamp after which this challenge can no longer be authorized. + example: '2025-10-03T12:05:00Z' + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor this challenge was issued for. Defaults to `SMS_OTP`. + availableFactors: + type: array + description: The factors the customer may use to satisfy this challenge. + items: + $ref: '#/components/schemas/ScaFactor' + example: + - SMS_OTP + purpose: + type: + - string + - 'null' + description: Optional, informational label for what this particular challenge in the sequence authorizes — useful for step UX (e.g. "Authorize the currency conversion" vs "Authorize the payout"). Known values include `CURRENCY_CONVERSION`, `PAYOUT`, and `TRANSFER`, but the set is **non-exhaustive and may grow** — treat unrecognized values as a generic authorization step and do not branch program logic on it. Omitted when steps are not distinguished (e.g. a single-authorization flow). + example: PAYOUT + passkeyAssertionOptions: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion request options (including the relying-party id, challenge, and allowed credentials), present only when `factor` is `PASSKEY`. Pass to the device's WebAuthn API to produce the assertion submitted back in `ScaAuthorization.passkeyAssertion`. + passkeyAllowedOrigins: + type: + - array + - 'null' + items: + type: string + description: The origins the WebAuthn ceremony may run against. Populated for enrollment and login passkey challenges; the origin the assertion is produced against must be one of these and echoed back as `ScaAuthorization.origin`. Per-transaction passkey challenges omit this (they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin` for how to source the origin in that case. + example: + - https://app.example.com + BeneficiaryTrustStart: type: object - description: Request body for confirming an email or phone verification challenge. - required: - - code + description: The SCA challenge (if any) a caller authorizes to finish trusting (or untrusting) a beneficiary. The beneficiary is identified by its `externalAccountId`, so the confirm call needs no separate handle. `scaChallenge` is omitted when no challenge is issued; the caller then confirms without a `challengeId`. properties: + scaChallenge: + $ref: '#/components/schemas/ScaChallenge' + description: The SCA challenge to satisfy on the confirm call. Omitted when no challenge is issued. + BeneficiaryTrustConfirmRequest: + type: object + description: Confirms trusting or untrusting a beneficiary by submitting the SCA proof. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`) and, when the start issued one, the `challengeId`. The beneficiary is identified by the `externalAccountId` in the path — no separate handle is needed. + anyOf: + - required: + - code + - required: + - passkeyAssertion + - origin + properties: + challengeId: + type: + - string + - 'null' + description: The challenge handle from the trust start's `scaChallenge`, when one was issued. Omit when the start returned no challenge. code: - type: string - description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the challenge's assertion options. Required when satisfying a `PASSKEY` challenge. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against. Required alongside `passkeyAssertion`; omit it for the `code` path. + example: https://app.example.com + BeneficiaryTrustConfirm: + type: object + description: The result of a confirm-trust / confirm-untrust call. `trusted` is `true` after a successful trust and `false` after a successful untrust. + required: + - trusted + properties: + trusted: + type: boolean + description: Whether the beneficiary is now trusted. `true` after a successful trust, `false` after a successful untrust. InternalAccountType: title: Internal Account Type type: string @@ -11007,19 +13969,6 @@ components: - `FROZEN`: The account cannot send or receive payments. Grid may freeze an account in response to compliance or fraud signals; payments are blocked while the account remains frozen. - `FAILED`: The account could not be provisioned. Grid was unable to create the underlying account, so it cannot send or receive payments and requires remediation. example: ACTIVE - CurrencyAmount: - type: object - required: - - amount - - currency - properties: - amount: - type: integer - format: int64 - description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) - example: 12550 - currency: - $ref: '#/components/schemas/Currency' PaymentAccountType: type: string enum: @@ -11045,10 +13994,12 @@ components: - LIGHTNING - SOLANA_WALLET - TRON_WALLET + - PLASMA_WALLET - POLYGON_WALLET - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET + - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -11081,10 +14032,43 @@ components: minLength: 9 maxLength: 9 pattern: ^[0-9]{9}$ + bankName: + type: string + description: The name of the financial institution holding the account. Optional on every rail, and recommended for wires, where it identifies the beneficiary's institution on the payment message. + example: Chase Bank + minLength: 1 + maxLength: 140 + bankAccountType: + type: string + description: Whether the account is a checking or a savings account. Optional on every rail; when omitted, the account is treated as a checking account. + enum: + - CHECKING + - SAVINGS + example: CHECKING + intermediaryBankName: + type: string + description: The name of the intermediary financial institution, for accounts reachable only through a correspondent bank. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: JPMorgan Chase Bank + minLength: 1 + maxLength: 140 + intermediaryRoutingNumber: + type: string + description: The ABA routing number of the intermediary financial institution. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: '021000021' + minLength: 9 + maxLength: 9 + pattern: ^[0-9]{9}$ + fiToFiInformation: + type: string + description: Bank-to-bank instructions carried alongside the payment. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: /BNF/Invoice 4471 + maxLength: 210 example: accountType: USD_ACCOUNT accountNumber: '1234567890' routingNumber: '021000021' + bankName: Chase Bank + bankAccountType: CHECKING UsdAccountInfo: allOf: - $ref: '#/components/schemas/UsdAccountInfoBase' @@ -12057,6 +15041,32 @@ components: description: Type of asset enum: - USDT + PlasmaWalletInfo: + type: object + required: + - address + - accountType + properties: + accountType: + type: string + enum: + - PLASMA_WALLET + address: + type: string + description: Plasma wallet address + example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + PaymentPlasmaWalletInfo: + title: Plasma Wallet + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletInfo' + - type: object + properties: + assetType: + type: string + description: Type of asset + enum: + - USDT PolygonWalletInfo: type: object required: @@ -12135,6 +15145,29 @@ components: description: Type of asset enum: - USDC + - USDT + PaymentBitcoinDepositAddressInfo: + title: Bitcoin L1 Deposit Address + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - type: object + required: + - address + properties: + accountType: + type: string + enum: + - BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: @@ -13512,9 +16545,11 @@ components: - $ref: '#/components/schemas/PaymentLightningInvoiceInfo' - $ref: '#/components/schemas/PaymentSolanaWalletInfo' - $ref: '#/components/schemas/PaymentTronWalletInfo' + - $ref: '#/components/schemas/PaymentPlasmaWalletInfo' - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' + - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -13539,9 +16574,11 @@ components: LIGHTNING: '#/components/schemas/PaymentLightningInvoiceInfo' SOLANA_WALLET: '#/components/schemas/PaymentSolanaWalletInfo' TRON_WALLET: '#/components/schemas/PaymentTronWalletInfo' + PLASMA_WALLET: '#/components/schemas/PaymentPlasmaWalletInfo' POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' + BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -13723,6 +16760,7 @@ components: - BASE_WALLET - ETHEREUM_WALLET - LIGHTNING + - PLASMA_WALLET - POLYGON_WALLET - SOLANA_WALLET - SPARK_WALLET @@ -15828,6 +18866,11 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletInfo' + PlasmaWalletExternalAccountInfo: + title: Plasma Wallet + allOf: + - $ref: '#/components/schemas/BaseExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletInfo' SolanaWalletExternalAccountInfo: title: Solana Wallet allOf: @@ -15937,6 +18980,7 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -15986,6 +19030,7 @@ components: LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' LIGHTNING_ACCOUNT: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' + PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -16855,6 +19900,7 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -16903,6 +19949,7 @@ components: ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo' LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' + PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -17395,6 +20442,7 @@ components: enum: - CREATED - PENDING + - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - REJECTED @@ -17408,6 +20456,7 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -17447,6 +20496,7 @@ components: - BASE - POLYGON - TRON + - PLASMA - SPARK description: The blockchain network an on-chain transaction settled on. Whether this is the mainnet or a test network (e.g. Solana devnet) is determined by your platform's environment — sandbox platforms operate on test networks, production platforms on mainnet — mirroring how `cryptoNetwork` is interpreted elsewhere in the API. example: SOLANA @@ -17511,6 +20561,43 @@ components: mapping: ACCOUNT: '#/components/schemas/AccountTransactionDestination' UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionDestination' + Refund: + type: object + required: + - reference + - initiatedAt + - status + properties: + reference: + type: string + description: The unique reference ID of the refund + example: UMA-Q12345-REFUND + initiatedAt: + type: string + format: date-time + description: When the refund was initiated + example: '2025-08-15T14:30:00Z' + settledAt: + type: string + format: date-time + description: When the refund was settled + example: '2025-08-15T14:35:00Z' + status: + type: string + enum: + - PENDING + - COMPLETED + - FAILED + description: Current status of the refund + example: COMPLETED + reason: + type: string + enum: + - TRANSACTION_FAILED + - USER_CANCELLATION + - TIMEOUT + description: Reason for the refund + example: TRANSACTION_FAILED CounterpartyInformation: type: object description: Additional information about the counterparty, if available and relevant to the transaction and platform. @@ -17545,11 +20632,11 @@ components: $ref: '#/components/schemas/TransactionDestinationOneOf' customerId: type: string - description: System ID of the customer (sender for outgoing, recipient for incoming) + description: System ID of the customer this transaction belongs to example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific ID of the customer (sender for outgoing, recipient for incoming) + description: Platform-specific ID of the customer this transaction belongs to example: 18d3e5f7b4a9c2 settledAt: type: string @@ -17579,6 +20666,21 @@ components: type: string description: Optional memo or description for the payment example: 'Payment for invoice #1234' + sentAmount: + $ref: '#/components/schemas/CurrencyAmount' + description: Amount sent in the sender's currency + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 1.08 + quoteId: + type: string + description: The ID of the quote that was used to trigger this payment + example: Quote:019542f5-b3e7-1d02-0000-000000000006 + refund: + $ref: '#/components/schemas/Refund' + description: The refund if transaction was refunded. counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' TransactionSourceType: @@ -17694,65 +20796,32 @@ components: example: '021000020123456' onChainTransaction: $ref: '#/components/schemas/OnChainTransaction' - description: On-chain transaction that delivered the funding, when the funds arrived from an external crypto wallet. Populated once the crypto transfer has settled. - description: Transaction was funded using an external funding source. All originator fields are optional and populated on a best-effort basis depending on what the funding source provides. - TransactionSourceOneOf: - oneOf: - - $ref: '#/components/schemas/AccountTransactionSource' - - $ref: '#/components/schemas/UmaAddressTransactionSource' - - $ref: '#/components/schemas/RealtimeFundingTransactionSource' - discriminator: - propertyName: sourceType - mapping: - ACCOUNT: '#/components/schemas/AccountTransactionSource' - UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionSource' - REALTIME_FUNDING: '#/components/schemas/RealtimeFundingTransactionSource' - ReconciliationInstructions: - type: object - minProperties: 1 - description: Instructions for reconciling a payment with this transaction. For the on-chain transaction to or from an external crypto wallet that is the transaction's own source or destination, use the `onChainTransaction` on the relevant source or destination instead. - properties: - reference: - type: string - description: Unique reference code to include with the payment to match it with the correct incoming transaction, when available. - example: UMA-Q12345-REF - transactionHash: - type: string - description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. - example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' - IncomingRateDetails: - description: 'Details about the rate and fees for an incoming transaction. Note: `gridApiFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' - type: object - required: - - gridApiMultiplier - - gridApiFixedFee - - gridApiVariableFeeRate - - gridApiVariableFeeAmount - properties: - gridApiMultiplier: - type: number - format: double - description: The underlying multiplier from the mSATS to the receiving currency, including variable fees. - exclusiveMinimum: 0 - example: 0.925 - gridApiFixedFee: - type: integer - format: int64 - description: The fixed fee charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). - minimum: 0 - example: 10 - gridApiVariableFeeRate: - type: number - format: double - description: The variable fee rate charged by the Grid product to execute the quote as a percentage of the receiving currency amount. - minimum: 0 - example: 0.003 - gridApiVariableFeeAmount: - type: number - format: int64 - description: The variable fee amount charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). This is the receiving amount times gridApiVariableFeeRate. - minimum: 0 - example: 30 + description: On-chain transaction that delivered the funding, when the funds arrived from an external crypto wallet. Populated once the crypto transfer has settled. + description: Transaction was funded using an external funding source. All originator fields are optional and populated on a best-effort basis depending on what the funding source provides. + TransactionSourceOneOf: + oneOf: + - $ref: '#/components/schemas/AccountTransactionSource' + - $ref: '#/components/schemas/UmaAddressTransactionSource' + - $ref: '#/components/schemas/RealtimeFundingTransactionSource' + discriminator: + propertyName: sourceType + mapping: + ACCOUNT: '#/components/schemas/AccountTransactionSource' + UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionSource' + REALTIME_FUNDING: '#/components/schemas/RealtimeFundingTransactionSource' + ReconciliationInstructions: + type: object + minProperties: 1 + description: Instructions for reconciling a payment with this transaction. For the on-chain transaction to or from an external crypto wallet that is the transaction's own source or destination, use the `onChainTransaction` on the relevant source or destination instead. + properties: + reference: + type: string + description: Unique reference code to include with the payment to match it with the correct incoming transaction, when available. + example: UMA-Q12345-REF + transactionHash: + type: string + description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. + example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' IncomingTransactionFailureReason: type: string enum: @@ -17786,15 +20855,12 @@ components: fees: type: integer format: int64 - description: The total fees available from the receive quote in the smallest unit of the receiving currency (eg. cents). + description: The total fees available from the receive quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Included for all transactions except those with "CREATED" status - rateDetails: - $ref: '#/components/schemas/IncomingRateDetails' - description: Details about the rate and fees for the transaction. failureReason: $ref: '#/components/schemas/IncomingTransactionFailureReason' description: If the transaction failed, this field provides the reason for failure. @@ -17802,6 +20868,7 @@ components: type: string enum: - PENDING + - PENDING_AUTHORIZATION - EXPIRED - PROCESSING - COMPLETED @@ -17812,47 +20879,11 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | | `FAILED` | Something went wrong — accompanied by a `failureReason` | - Refund: - type: object - required: - - reference - - initiatedAt - - status - properties: - reference: - type: string - description: The unique reference ID of the refund - example: UMA-Q12345-REFUND - initiatedAt: - type: string - format: date-time - description: When the refund was initiated - example: '2025-08-15T14:30:00Z' - settledAt: - type: string - format: date-time - description: When the refund was settled - example: '2025-08-15T14:35:00Z' - status: - type: string - enum: - - PENDING - - COMPLETED - - FAILED - description: Current status of the refund - example: COMPLETED - reason: - type: string - enum: - - TRANSACTION_FAILED - - USER_CANCELLATION - - TIMEOUT - description: Reason for the refund - example: TRANSACTION_FAILED OutgoingRateDetails: description: 'Details about the rate and fees for an outgoing transaction or quote. Note: `counterpartyFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' type: object @@ -17908,7 +20939,15 @@ components: - LIGHTNING_PAYMENT_FAILED - FUNDING_AMOUNT_MISMATCH - COUNTERPARTY_POST_TX_FAILED - description: Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. + - SCA_NOT_COMPLETED + - EXECUTION_FAILED_POST_DEBIT + - SETTLEMENT_FAILED + - TIMEOUT + - MANUAL_REFUND + description: |- + Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. + + `SCA_NOT_COMPLETED` means the customer did not satisfy the Strong Customer Authentication challenge before it expired, so the transaction was never authorized and no funds were moved. Only occurs for customers in a region where SCA is required (e.g. the EU). Create a new quote to try again, and have the customer authorize it while the challenge is live. RailSelectionMode: type: string enum: @@ -17940,24 +20979,21 @@ components: receivedAmount: $ref: '#/components/schemas/CurrencyAmount' description: Amount to be received by recipient in the recipient's currency - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 1.08 fees: type: integer format: int64 description: The fees associated with the quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 + platformFees: + type: integer + format: int64 + description: The portion of `fees` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `fees`. + minimum: 0 + example: 5 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Reconciliation details for this transaction. For the on-chain hash of a crypto payout to an external wallet, see the destination's `onChainTransaction` instead. - quoteId: - type: string - description: The ID of the quote that was used to trigger this payment - example: Quote:019542f5-b3e7-1d02-0000-000000000006 paymentInstructions: type: array description: Payment instructions for executing the payment. @@ -17979,9 +21015,6 @@ components: assetType: BTC address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - refund: - $ref: '#/components/schemas/Refund' - description: The refund if transaction was refunded. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. @@ -18208,6 +21241,24 @@ components: description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. allOf: - $ref: '#/components/schemas/PaymentRail' + PurposeOfPayment: + type: string + description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). + enum: + - GIFT + - SELF + - GOODS_OR_SERVICES + - EDUCATION + - HEALTH_OR_MEDICAL + - REAL_ESTATE_PURCHASE + - TAX_PAYMENT + - LOAN_PAYMENT + - UTILITY_BILL + - DONATION + - TRAVEL + - FAMILY_SUPPORT + - SALARY_PAYMENT + - OTHER TransferOutRequest: type: object required: @@ -18230,6 +21281,8 @@ components: maxLength: 80 description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' example: '12345' + purposeOfPayment: + $ref: '#/components/schemas/PurposeOfPayment' CurrencyPreference: type: object required: @@ -18489,11 +21542,12 @@ components: type: string enum: - PENDING + - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - FAILED - EXPIRED - description: Current status of the quote + description: 'Current status of the quote. `PENDING_AUTHORIZATION` occurs only for customers in a region where Strong Customer Authentication is required (e.g. EU): the quote carries an `scaChallenge` that must be authorized before execution, and for realtime-funding sources `paymentInstructions` are withheld until it is satisfied.' example: PENDING createdAt: type: string @@ -18537,6 +21591,12 @@ components: description: 'The fees associated with the quote in the smallest unit of the sending currency (eg. cents). Note: this value may fluctuate between quotes — some underlying fee components are defined in the receiving currency, so their equivalent in the sending currency moves with the FX rate. The fees shown here are locked only for the lifetime of this quote.' minimum: 0 example: 10 + platformFeesIncluded: + type: integer + format: int64 + description: The portion of `feesIncluded` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `feesIncluded`. May be omitted from payloads produced before platform fees existed. + minimum: 0 + example: 5 paymentInstructions: type: array description: Payment instructions for executing the payment. This is not required when using an internal account source. @@ -18562,41 +21622,22 @@ components: type: string description: The ID of the transaction created from this quote. example: Transaction:019542f5-b3e7-1d02-0000-000000000005 - remittanceInformation: - type: string - maxLength: 80 - description: 'Free-form information about the payment that travels with it to the recipient, as provided on the quote request. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' - example: '12345' counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' description: Additional information about the counterparty, if available and required by the platform in their configuration. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. + scaChallenge: + $ref: '#/components/schemas/ScaChallenge' + readOnly: true + description: 'Present only while `status` is `PENDING_AUTHORIZATION`: the Strong Customer Authentication challenge to satisfy before this quote can be executed (or, for realtime-funding sources, before `paymentInstructions` are issued). Omitted for customers outside SCA-regulated regions (non-EU).' QuoteLockSide: type: string enum: - SENDING - RECEIVING description: The side of the quote which should be locked and specified in the `lockedCurrencyAmount`. For example, if I want to send exactly $5 MXN from my wallet, I would set this to "sending", and the `lockedCurrencyAmount` to 500 (in cents). If I want the receiver to receive exactly $10 USD, I would set this to "receiving" and the `lockedCurrencyAmount` to 10000 (in cents). - PurposeOfPayment: - type: string - description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). - enum: - - GIFT - - SELF - - GOODS_OR_SERVICES - - EDUCATION - - HEALTH_OR_MEDICAL - - REAL_ESTATE_PURCHASE - - TAX_PAYMENT - - LOAN_PAYMENT - - UTILITY_BILL - - DONATION - - TRAVEL - - FAMILY_SUPPORT - - SALARY_PAYMENT - - OTHER QuoteRequest: type: object required: @@ -18642,6 +21683,9 @@ components: example: '12345' purposeOfPayment: $ref: '#/components/schemas/PurposeOfPayment' + scaFactor: + $ref: '#/components/schemas/ScaFactor' + description: Optional preferred factor for a Strong Customer Authentication challenge issued at quote creation. Only relevant for a realtime-funding source in a region where SCA is required (e.g. EU); ignored otherwise. Valid values are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected. When the quote is returned in `PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`. senderCustomerInfo: type: object additionalProperties: true @@ -18651,6 +21695,41 @@ components: example: FULL_NAME: Jane Receiver NATIONALITY: FR + ExecuteQuoteRequest: + type: object + description: Optional body for executing a quote. Only needed to request a specific Strong Customer Authentication factor (`scaFactor`) for the challenge this call issues; omit the body entirely otherwise. + properties: + scaFactor: + $ref: '#/components/schemas/ScaFactor' + description: Optional preferred factor for the Strong Customer Authentication challenge this call issues. Only relevant for customers in a region where SCA is required (e.g. EU); ignored otherwise. Valid values for a per-transaction challenge are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected here. Omit to default to `SMS_OTP`. + ScaAuthorization: + type: object + description: Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for `SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a `passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound to the origin it was produced against, and a passkey confirmation is rejected without it. + properties: + code: + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. In sandbox, the code is always `123456`. + example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the challenge's `passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against. **Required** alongside `passkeyAssertion`; omit it for the `code` path. When the challenge lists `passkeyAllowedOrigins` (enrollment / login challenges), it must be one of those. A per-transaction passkey challenge carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in that case supply the origin your app invoked the WebAuthn API from, which must match the relying party in `passkeyAssertionOptions`. + example: https://app.example.com + endUserIpAddress: + type: + - string + - 'null' + description: 'The IP address of the end user''s device authorizing this operation, forwarded to the SCA provider where it feeds risk assessment and any transaction-risk exemption. Supply the customer''s address, not your server''s. Optional: the provider records it for money-movement authorizations, and ignores it for beneficiary trust changes.' + example: 203.0.113.42 TransactionListResponse: type: object required: @@ -18693,6 +21772,13 @@ components: type: string description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes. example: RESTRICTED_JURISDICTION + CancelTransactionRequest: + type: object + properties: + reason: + type: string + description: Optional reason for cancelling the transaction. This is just for debugging purposes or can be used for a platform's own purposes. + example: REQUESTED_AFTER_HOURS EstimateCryptoWithdrawalFeeRequest: type: object required: @@ -19623,33 +22709,6 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the `encryptedSessionSigningKey` must not be used to sign further requests. example: '2026-04-09T15:30:01Z' - Error429: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 429 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | - enum: - - RATE_LIMITED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. @@ -20579,7 +23638,7 @@ components: - amount - currency - merchant - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the endpoint documentation for the suffix table. + description: 'Sandbox-only request body shared by the card authorization-family simulate endpoints: `simulate/authorization`, `simulate/credit_authorization`, `simulate/financial_authorization`, `simulate/financial_credit_authorization`, and `simulate/credit_authorization_advice`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` documentation for the suffix table.' properties: amount: type: integer @@ -20591,6 +23650,16 @@ components: $ref: '#/components/schemas/Currency' merchant: $ref: '#/components/schemas/CardMerchant' + SandboxCardSimulationResponse: + type: object + required: + - issuerTransactionToken + description: Response body for the sandbox card-event simulators. The simulate call pokes the card issuer's sandbox; the resulting card operation is delivered asynchronously via the issuer's events webhook, never synchronously in this response. + properties: + issuerTransactionToken: + type: string + description: The card issuer's transaction token for the simulated event. Correlates the eventual webhook-delivered card operation back to this simulate call. + example: f3a1c2d4-5b6e-7890-abcd-ef0123456789 SandboxCardClearingRequest: type: object required: @@ -20625,6 +23694,41 @@ components: description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). exclusiveMinimum: 0 example: 1500 + SandboxCardBalanceInquiryRequest: + type: object + required: + - merchant + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/balance_inquiry`. Drives a balance-inquiry authorization against the card. A balance inquiry is always a `0`-amount authorization, so it carries no `amount`. + properties: + merchant: + $ref: '#/components/schemas/CardMerchant' + SandboxCardAuthorizationAdviceRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization_advice`. Drives an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` the advice re-sizes. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: The new *total* authorized amount, in the smallest unit of the transaction's currency, that the advice re-sizes the authorization to. + exclusiveMinimum: 0 + example: 2000 + SandboxCardTransactionRefRequest: + type: object + required: + - cardTransactionId + description: Sandbox-only request body for simulate endpoints keyed only by an existing card transaction — currently `POST /sandbox/cards/{id}/simulate/return_reversal`. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to act against. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 StablecoinIssuanceStatus: type: string enum: @@ -20904,67 +24008,16 @@ components: SAME_DAY_ACH_DEBIT: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalFundingSource' PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceFundingSource' - StablecoinMintDestinationBase: + StablecoinMintDestination: type: object + description: Account that receives the minted stablecoin. External and Grid-managed accounts are represented as a single account reference, matching the `accountId` pattern used elsewhere in the API; the id prefix (`ExternalAccount:` or `InternalAccount:`) determines which. In the initial Brale-backed flow this must reference an active Spark external account. required: - - type + - accountId properties: - type: + accountId: type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - example: EXTERNAL_ACCOUNT - StablecoinExternalAccountMintDestination: - title: External Account - allOf: - - $ref: '#/components/schemas/StablecoinMintDestinationBase' - - type: object - required: - - type - - externalAccountId - properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - description: External account mint destination. Must reference an active Spark external account. - example: EXTERNAL_ACCOUNT - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - description: External account mint destination. - StablecoinGridInternalMintDestination: - title: Grid Internal Account - allOf: - - $ref: '#/components/schemas/StablecoinMintDestinationBase' - - type: object - required: - - type - - accountId - properties: - type: - type: string - enum: - - GRID_INTERNAL_ACCOUNT - description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. - example: GRID_INTERNAL_ACCOUNT - accountId: - type: string - description: Grid internal receiving account id. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 - description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. - StablecoinMintDestination: - oneOf: - - $ref: '#/components/schemas/StablecoinExternalAccountMintDestination' - - $ref: '#/components/schemas/StablecoinGridInternalMintDestination' - discriminator: - propertyName: type - mapping: - EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountMintDestination' - GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalMintDestination' + description: Grid account receiving the minted stablecoin. Accepts an `ExternalAccount:` id (initial Brale-backed flow) or an `InternalAccount:` id for Grid-managed destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 StablecoinMintRequest: type: object required: @@ -21064,24 +24117,14 @@ components: valueType: ACME StablecoinOperationDestination: type: object - description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint and burn destination request variants) so the field is unambiguously deserializable regardless of operation type: `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' + description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged (flat) shape so the field is unambiguously deserializable regardless of operation type: `accountId` identifies the receiving account (its `ExternalAccount:` / `InternalAccount:` prefix disambiguates external vs Grid-managed), and `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' required: - - type + - accountId properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Destination variant. - externalAccountId: - type: string - description: Grid `ExternalAccount` id, when the destination is an external account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 accountId: type: string - description: Grid internal account id, when the destination is a Grid-managed account. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: Grid account that received the funds. An `ExternalAccount:` id for external destinations or an `InternalAccount:` id for Grid-managed destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 rail: type: string enum: @@ -21462,6 +24505,9 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED + - INCOMING_PAYMENT.REFUND_PENDING + - INCOMING_PAYMENT.REFUND_COMPLETED + - INCOMING_PAYMENT.REFUND_FAILED - CUSTOMER.KYC_APPROVED - CUSTOMER.KYC_REJECTED - CUSTOMER.KYC_PENDING @@ -21482,6 +24528,11 @@ components: - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - CARD.FUNDING_SOURCE_CHANGE + - CARD_TRANSACTION.AUTHORIZED + - CARD_TRANSACTION.PARTIALLY_SETTLED + - CARD_TRANSACTION.SETTLED + - CARD_TRANSACTION.REFUNDED + - CARD_TRANSACTION.EXCEPTION - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -21542,6 +24593,9 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED + - INCOMING_PAYMENT.REFUND_PENDING + - INCOMING_PAYMENT.REFUND_COMPLETED + - INCOMING_PAYMENT.REFUND_FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -21584,6 +24638,7 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING + - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED @@ -21702,6 +24757,23 @@ components: type: string enum: - CARD.FUNDING_SOURCE_CHANGE + CardTransactionWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/CardTransaction' + type: + type: string + enum: + - CARD_TRANSACTION.AUTHORIZED + - CARD_TRANSACTION.PARTIALLY_SETTLED + - CARD_TRANSACTION.SETTLED + - CARD_TRANSACTION.REFUNDED + - CARD_TRANSACTION.EXCEPTION requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi.yaml b/openapi.yaml index 6671a208b..a6069a95d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -23,6 +23,8 @@ tags: description: Customer management endpoints for creating and updating customer information - name: Contact Verification description: Endpoints for verifying a customer's email and phone via one-time codes. Required only for customers whose payment provider mandates contact verification (e.g. EU customers); other providers return 409. + - name: Strong Customer Authentication + description: Endpoints for authorizing money-movement operations that require Strong Customer Authentication. Relevant only for customers in a region where SCA is required (e.g. EU); customers outside SCA-regulated regions never see an SCA challenge and these endpoints return 409. - name: KYC/KYB Verifications description: Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. - name: Documents @@ -125,6 +127,18 @@ paths: sendFromEmailSenderName: Acme Notifications replyToEmailAddress: support@acme.com logoUrl: https://acme.com/logo.png + cardTokenization2faConfig: + displayName: Acme + logoUrl: https://acme.com/card-email-logo.png + email: + fromAddress: cards@acme.com + fromName: Acme Cards + replyToAddress: support@acme.com + subject: Your Acme card verification code + bodyText: Use this code to finish adding your Acme card to your digital wallet. + sms: + templateSid: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bodyText: Use this code to finish adding your Acme card to your digital wallet. responses: '200': description: Configuration updated successfully @@ -412,9 +426,9 @@ paths: address: line1: 123 Market Street line2: Suite 400 - city: San Francisco - state: CA - postalCode: '94105' + city: Seattle + state: WA + postalCode: '98101' country: US responses: '201': @@ -674,9 +688,9 @@ paths: - USDC address: line1: 456 Market St - city: San Francisco - state: CA - postalCode: '94103' + city: Seattle + state: WA + postalCode: '98101' country: US businessUpdate: summary: Update business customer example @@ -691,9 +705,9 @@ paths: taxId: EIN-123456789 address: line1: 100 Technology Parkway - city: Palo Alto - state: CA - postalCode: '94304' + city: Bellevue + state: WA + postalCode: '98004' country: US embeddedWalletEmailUpdate: summary: Embedded Wallet email update request (both steps) @@ -1125,180 +1139,97 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/internal-accounts: + /sca/factors: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer whose factors are listed or enrolled. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 get: - summary: List Customer internal accounts + summary: List enrolled SCA factors description: | - Retrieve a list of internal accounts with optional filtering parameters. Returns all - internal accounts that match the specified filters. If no filters are provided, returns all internal accounts - (paginated). + List the Strong Customer Authentication factors the customer has enrolled. - Internal accounts are created automatically when a customer is created based on the platform configuration. - operationId: listCustomerInternalAccounts + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: listScaFactors tags: - - Internal Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by internal accounts associated with a specific customer - required: false - schema: - type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string responses: '200': - description: Successful operation + description: The customer's enrolled SCA factors. content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ScaFactorList' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /platform/internal-accounts: - get: - summary: List platform internal accounts - description: | - Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. - - These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. - operationId: listPlatformInternalAccounts - tags: - - Internal Accounts - security: - - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: type - in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. - required: false - schema: - $ref: '#/components/schemas/InternalAccountType' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/PlatformInternalAccountListResponse' - '400': - description: Bad request - Invalid parameters + '404': + description: Customer not found content: application/json: schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. content: application/json: schema: - $ref: '#/components/schemas/Error401' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts: - get: - summary: List Customer external accounts + post: + summary: Start SCA factor enrollment description: | - Retrieve a list of external accounts with optional filtering parameters. Returns all - external accounts that match the specified filters. If no filters are provided, returns all external accounts - (paginated). + Begin enrolling an SCA factor for the customer. Enrollment covers the + explicit, opt-in factors a customer chooses to add — the request body's + `type` selects `TOTP` or `PASSKEY`. Returns the factor-specific material + needed to finish via `POST /sca/factors/confirm`. - External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. - operationId: listCustomerExternalAccounts + `SMS_OTP` is implicit and is not enrolled through this endpoint. Every + customer in an SCA-regulated region has a verified phone number from + customer creation (via the Contact Verification flows — + `POST /customers/{customerId}/verify-phone` and `.../verify-phone/confirm`), + so SMS is always available as a factor with no extra setup and appears + among the customer's enrolled factors in `GET /sca/factors`. + + A customer may have **only one passkey**. Starting a passkey enrollment when + one is already enrolled returns `409` (`PASSKEY_ALREADY_ENROLLED`) — delete it + via `DELETE /sca/factors/{credentialId}` first. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startScaFactorEnrollment tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by external accounts associated with a specific customer - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaFactorEnrollRequestOneOf' responses: '200': - description: Successful operation + description: Enrollment started; the factor-specific completion material is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/ScaFactorEnrollStartOneOf' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1309,18 +1240,52 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer (`CONFLICT`), or a passkey enrollment was requested while one is already enrolled (`PASSKEY_ALREADY_ENROLLED`) — only one passkey per customer is supported. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /sca/factors/confirm: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer confirming a factor enrollment. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Add a new external account - description: Register a new external bank account for a customer. - operationId: createCustomerExternalAccount + summary: Confirm SCA factor enrollment + description: | + Finalize the factor enrollment started by `POST /sca/factors`. The request + body is discriminated by `type`: for `TOTP`, submit the shared `secret` from + the start call plus the first `code`; for `PASSKEY`, submit the WebAuthn + `credential` the device produced plus the `origin` it was produced against. + The threaded secret/credential binds the confirmation to its enrollment, so + no separate id is needed. + + A TOTP confirmation returns one-time recovery codes (shown once); a passkey + confirmation returns the enrolled factor. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the TOTP code is always `123456`. + operationId: confirmScaFactorEnrollment tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -1328,48 +1293,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + $ref: '#/components/schemas/ScaFactorConfirmRequestOneOf' responses: - '201': - description: External account created successfully + '200': + description: Factor enrolled; the factor-specific result is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/ScaFactorConfirmResponseOneOf' '400': - description: Bad request + description: Invalid or incorrect confirmation proof content: application/json: schema: @@ -1380,8 +1313,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: SCA is not required for this customer. content: application/json: schema: @@ -1392,29 +1331,36 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}: + /sca/factors/{credentialId}: parameters: - - name: externalAccountId + - name: customerId + in: query + description: The unique identifier of the customer whose factor is being deleted. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: credentialId in: path - description: System-generated unique external account identifier + description: The credential id of the enrolled factor to delete (from the factor's `credentialId`). required: true schema: type: string - get: - summary: Get customer external account by ID - description: Retrieve a customer external account by its system-generated ID - operationId: getCustomerExternalAccountById + delete: + summary: Delete an enrolled SCA factor + description: | + Delete an enrolled SCA factor by its credential id. Today only `PASSKEY` + factors carry a `credentialId` and are deletable this way. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: deleteScaFactor tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' + '204': + description: Factor deleted; no content is returned. '401': description: Unauthorized content: @@ -1422,28 +1368,68 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or factor not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete customer external account by ID - description: Delete a customer external account by its system-generated ID - operationId: deleteCustomerExternalAccountById - tags: - - External Accounts - security: - - BasicAuth: [] + /sca/login/start: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer starting an SCA login. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Start an SCA login + description: | + Begin an SCA login for the customer with the chosen factor, opening the + end-user SCA session (an exemption gating read / account access beyond the + per-transaction window). Returns factor-specific material: `SMS_OTP` + dispatches a code and returns a `challengeId` + `expiresAt`; `TOTP` returns + only the factor (the customer reads the code from their app); `PASSKEY` + returns WebAuthn `passkeyOptions`. Complete with + `POST /sca/login/complete`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startScaLogin + tags: + - Strong Customer Authentication + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginStartRequest' responses: - '204': - description: External account deleted successfully + '200': + description: SCA login started; factor-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginStart' + '400': + description: Invalid or unknown factor + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1451,60 +1437,63 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts: - get: - summary: List platform external accounts + /sca/login/complete: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer completing an SCA login. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Complete an SCA login description: | - Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. + Finalize an SCA login by submitting the proof for the started factor + (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for + `PASSKEY`), echoing the `challengeId` for `SMS_OTP`. Returns the + reported session status. - These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. - operationId: listPlatformExternalAccounts + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: completeScaLogin tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: currency - in: query - description: Filter by currency code - required: false - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaLoginCompleteRequest' responses: '200': - description: Successful operation + description: SCA login completed; the session status is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/ScaLoginComplete' '400': - description: Bad request - Invalid parameters + description: Invalid or expired proof content: application/json: schema: @@ -1515,18 +1504,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '423': + description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /sca/record-event: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer the security event is recorded for. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 post: - summary: Add a new platform external account - description: Register a new external bank account for the platform. - operationId: createPlatformExternalAccount + summary: Record a security event + description: | + Record a client-side security-relevant event for the customer with Grid's risk engine (e.g. a sign-in, a sensitive view), to + feed adaptive-authentication signals. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: recordSecurityEvent tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -1534,46 +1554,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - platformAccountId: ext_acc_123456 - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + $ref: '#/components/schemas/RecordSecurityEventRequest' responses: - '201': - description: External account created successfully + '200': + description: Event recorded; the customer's resulting login-security state is returned (including any lockout). content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/RecordSecurityEventResponse' '400': - description: Bad request + description: Invalid event type content: application/json: schema: @@ -1584,41 +1574,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - External account already exists + description: SCA is not required for this customer. content: application/json: schema: $ref: '#/components/schemas/Error409' + '423': + description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}: + /sca/factors/reset: parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier + - name: customerId + in: query + description: The unique identifier of the customer resetting a factor. required: true schema: type: string - get: - summary: Get platform external account by ID - description: Retrieve a platform external account by its system-generated ID - operationId: getPlatformExternalAccountById + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Start a 2FA reset + description: | + Begin recovering a lost enrolled factor via a liveness-gated, poll-based + flow. Opens the liveness check and returns a `resetId` plus the + opaque liveness handles (`livenessAccessToken` / `verificationLink`) the end + user completes it with. Poll + `GET /sca/factors/reset/{resetId}` until liveness + passes, then call the complete endpoint. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startTwoFactorReset tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResetStartRequest' responses: - '200': - description: Successful operation + '201': + description: Reset initiated; the reset handle and liveness material are returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/TwoFactorResetStart' + '400': + description: Invalid or unknown factor + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1626,28 +1649,62 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '429': + description: Too many reset attempts. Reset initiation is rate-limited to 5 per 24 hours per customer; retry after the window indicated by `Retry-After`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete platform external account by ID - description: Delete a platform external account by its system-generated ID - operationId: deletePlatformExternalAccountById + /sca/factors/reset/{resetId}: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer whose reset status is polled. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: resetId + in: path + description: The reset handle returned by the start call. + required: true + schema: + type: string + get: + summary: Get 2FA reset status + description: | + Poll the status of an in-progress 2FA reset until it reaches the liveness-passed value, after which the reset can be completed. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: getTwoFactorResetStatus tags: - - External Accounts + - Strong Customer Authentication security: - BasicAuth: [] responses: - '204': - description: External account deleted successfully + '200': + description: The current reset status. + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResetStatus' '401': description: Unauthorized content: @@ -1655,42 +1712,64 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Customer or reset not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners: + /sca/factors/reset/{resetId}/complete: + parameters: + - name: customerId + in: query + description: The unique identifier of the customer completing the reset. + required: true + schema: + type: string + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + - name: resetId + in: path + description: The reset handle returned by the start call. + required: true + schema: + type: string post: - summary: Create a beneficial owner + summary: Complete a 2FA reset description: | - Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. - operationId: createBeneficialOwner + Complete a 2FA reset once liveness has passed, clearing the lost factor so + the customer can re-enroll. + + For an `SMS_OTP` reset, supply the new `mobile` number in the body — completing + the reset enrolls it as the customer's number. Other factors need no body. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: completeTwoFactorReset tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerCreateRequest' + $ref: '#/components/schemas/TwoFactorResetCompleteRequest' responses: - '201': - description: Beneficial owner created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwner' + '204': + description: Reset completed; no content is returned. '400': - description: Bad request - Invalid parameters + description: Reset not ready (liveness not yet passed) content: application/json: schema: @@ -1702,57 +1781,54 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: Customer or reset not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List beneficial owners - description: | - Retrieve a list of beneficial owners for a business customer. - operationId: listBeneficialOwners - tags: - - KYC/KYB Verifications + /customers/external-accounts/{externalAccountId}/trust: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being trusted. + required: true + schema: + type: string + post: + summary: Start trusting a beneficiary + description: | + Begin trusting (whitelisting) an external account so future sends to it can + skip the per-transaction SCA ceremony. Returns the `scaChallenge` to satisfy + (when one is issued). Complete with + `POST /customers/external-accounts/{externalAccountId}/trust/confirm`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startBeneficiaryTrust + tags: + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: customerId - in: query - description: The business customer ID - required: true - schema: - type: string - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string responses: '200': - description: Successful operation + description: Beneficiary trust started; the SCA challenge (if any) is returned. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwnerListResponse' + $ref: '#/components/schemas/BeneficiaryTrustStart' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1763,35 +1839,67 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /beneficial-owners/{beneficialOwnerId}: - get: - summary: Get a beneficial owner - description: Retrieve details of a specific beneficial owner by ID. - operationId: getBeneficialOwner + /customers/external-accounts/{externalAccountId}/trust/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being trusted. + required: true + schema: + type: string + post: + summary: Confirm trusting a beneficiary + description: | + Finalize trusting a beneficiary (identified by the `externalAccountId` in the + path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or + `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when + one was issued. Returns `trusted: true`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: confirmBeneficiaryTrust tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: beneficialOwnerId - in: path - description: Beneficial owner ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' responses: '200': - description: Successful operation + description: Beneficiary trusted. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/BeneficiaryTrustConfirm' + '400': + description: Invalid or expired proof + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -1799,47 +1907,55 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - patch: - summary: Update a beneficial owner - description: Update details of a specific beneficial owner. Only provided fields are updated. - operationId: updateBeneficialOwner + /customers/external-accounts/{externalAccountId}/untrust: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being untrusted. + required: true + schema: + type: string + post: + summary: Start untrusting a beneficiary + description: | + Begin untrusting (removing the trusted mark from) an external account, so + future sends to it are dynamically linked and require the per-transaction SCA + ceremony again. Returns the `scaChallenge` to satisfy (when one is issued). + Complete with + `POST /customers/external-accounts/{externalAccountId}/untrust/confirm`. + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + operationId: startBeneficiaryUntrust tags: - - KYC/KYB Verifications + - Strong Customer Authentication security: - BasicAuth: [] - parameters: - - name: beneficialOwnerId - in: path - description: Beneficial owner ID - required: true - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Beneficial owner updated successfully + description: Beneficiary untrust started; the SCA challenge (if any) is returned. content: application/json: schema: - $ref: '#/components/schemas/BeneficialOwner' + $ref: '#/components/schemas/BeneficiaryTrustStart' '400': - description: Bad request - Invalid parameters + description: Invalid request content: application/json: schema: @@ -1851,40 +1967,62 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Beneficial owner not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /documents: + /customers/external-accounts/{externalAccountId}/untrust/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (beneficiary) being untrusted. + required: true + schema: + type: string post: - summary: Upload a document + summary: Confirm untrusting a beneficiary description: | - Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. + Finalize untrusting a beneficiary (identified by the `externalAccountId` in + the path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or + `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when + one was issued. Returns `trusted: false`. - Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. - operationId: uploadDocument + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS/TOTP code is always `123456`. + operationId: confirmBeneficiaryUntrust tags: - - Documents + - Strong Customer Authentication security: - BasicAuth: [] requestBody: - $ref: '#/components/requestBodies/DocumentUploadRequestBody' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest' responses: - '201': - description: Document uploaded successfully + '200': + description: Beneficiary untrusted. content: application/json: schema: - $ref: '#/components/schemas/Document' + $ref: '#/components/schemas/BeneficiaryTrustConfirm' '400': - description: Bad request - Invalid file type, size, or parameters + description: Invalid or expired proof content: application/json: schema: @@ -1896,33 +2034,56 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Document holder not found + description: Customer or external account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /customers/internal-accounts: get: - summary: List documents + summary: List Customer internal accounts description: | - Retrieve a list of documents with optional filtering by document holder. - operationId: listDocuments + Retrieve a list of internal accounts with optional filtering parameters. Returns all + internal accounts that match the specified filters. If no filters are provided, returns all internal accounts + (paginated). + + Internal accounts are created automatically when a customer is created based on the platform configuration. + operationId: listCustomerInternalAccounts tags: - - Documents + - Internal Accounts security: - BasicAuth: [] parameters: - - name: documentHolder + - name: currency in: query - description: Filter by document holder ID (Customer or BeneficialOwner) + description: Filter by currency code + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by internal accounts associated with a specific customer required: false schema: type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -1944,7 +2105,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DocumentListResponse' + $ref: '#/components/schemas/InternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -1963,74 +2124,107 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /documents/{documentId}: + /platform/internal-accounts: get: - summary: Get a document by ID - description: Retrieve details and metadata of a specific document by ID. - operationId: getDocument + summary: List platform internal accounts + description: | + Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer. + + These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes. + operationId: listPlatformInternalAccounts tags: - - Documents + - Internal Accounts security: - BasicAuth: [] parameters: - - name: documentId - in: path - description: Document ID - required: true + - name: currency + in: query + description: Filter by currency code + required: false schema: type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Document' - '401': - description: Unauthorized + $ref: '#/components/schemas/PlatformInternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Document not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - put: - summary: Replace a document + /customers/external-accounts: + get: + summary: List Customer external accounts description: | - Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. - operationId: replaceDocument + Retrieve a list of external accounts with optional filtering parameters. Returns all + external accounts that match the specified filters. If no filters are provided, returns all external accounts + (paginated). + + External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform. + operationId: listCustomerExternalAccounts tags: - - Documents + - External Accounts security: - BasicAuth: [] parameters: - - name: documentId - in: path - description: Document ID - required: true + - name: currency + in: query + description: Filter by currency code + required: false schema: type: string - requestBody: - $ref: '#/components/requestBodies/DocumentReplaceRequestBody' - responses: - '200': - description: Document replaced successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Document' + - name: customerId + in: query + description: Filter by external accounts associated with a specific customer + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountListResponse' '400': - description: Bad request - Invalid file type, size, or parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2041,51 +2235,79 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Document not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete a document - description: | - Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. - operationId: deleteDocument + post: + summary: Add a new external account + description: Register a new external bank account for a customer. + operationId: createCustomerExternalAccount tags: - - Documents + - External Accounts security: - BasicAuth: [] - parameters: - - name: documentId - in: path - description: Document ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '204': - description: Document deleted successfully - '401': - description: Unauthorized + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Document not found + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '409': - description: Conflict - Document cannot be deleted (already submitted for verification) + description: Conflict - External account already exists content: application/json: schema: @@ -2096,110 +2318,60 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications: - post: - summary: Submit customer for verification - description: | - Trigger KYC (individual) or KYB (business) verification for a customer. - The response indicates whether all required information has been provided. - If data is missing, the `errors` array describes exactly what needs to be - supplied before verification can proceed. - - Call this endpoint again after resolving errors to re-submit. - - ### What to collect for KYB - - Before submitting a `BUSINESS` customer, collect the following via - `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: - - **Business identifying information** - - Entity full legal name - - Doing Business As (DBA) name, if applicable - - Physical address — principal place of business - - Countries of operation - - Identification number — U.S. taxpayer identification number, or, for a - foreign business without one, alternative government-issued documentation - certifying the existence of the business - - **Ownership and control structure** — collected for **one control person** - (an individual with significant responsibility to control, manage, or - direct the legal entity) **and all beneficial owners** (every individual - who owns 25% or more, directly or indirectly). For each, provide: - - Full name - - Date of birth - - Address - - Identification number: - - U.S. persons — SSN or ITIN - - Non-U.S. persons — one or more of: ITIN, passport (with country of - issuance), alien identification card, or another government-issued - photo ID evidencing nationality or residence - - **Required documents** - - Company formation and existence documents (certificate of incorporation, - articles of association, etc.) - - Proof of ownership and control structure (organization and ownership - chart, shareholder agreements, operating agreements, register of members, - or certification of controlling person and beneficial owners) - - Proof of address dated within the last 3 months (utility bill, bank - statement, lease agreement, or official correspondence) - - Tax ID or equivalent identifying-number documents - - For non-U.S. beneficial owners — passport plus one additional - government-issued ID - operationId: createVerification + /customers/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string + get: + summary: Get customer external account by ID + description: Retrieve a customer external account by its system-generated ID + operationId: getCustomerExternalAccountById tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: | - Verification status returned. Check `verificationStatus` and `errors` to determine next steps. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Verification' - examples: - missingInfo: - summary: Verification blocked by missing data - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000001 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_FIELD - field: customer.address.line1 - reason: Business address line 1 is required - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 - type: MISSING_FIELD - field: personalInfo.birthDate - reason: Date of birth is required for beneficial owners - createdAt: '2025-10-03T12:00:00Z' - submitted: - summary: Verification submitted successfully - value: - id: Verification:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: IN_PROGRESS - errors: [] - createdAt: '2025-10-03T12:00:00Z' - '400': - description: Bad request - Invalid parameters + $ref: '#/components/schemas/ExternalAccount' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Delete customer external account by ID + description: |- + Delete a customer external account by its system-generated ID. + An account that is currently a trusted beneficiary for SCA cannot be deleted — untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete. + operationId: deleteCustomerExternalAccountById + tags: + - External Accounts + security: + - BasicAuth: [] + responses: + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -2207,39 +2379,42 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer not found + description: External account not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Returned with `BENEFICIARY_TRUSTED` when the external account is currently a trusted beneficiary. Untrust it first, then delete. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /platform/external-accounts: get: - summary: List verifications + summary: List platform external accounts description: | - Retrieve a list of verifications with optional filtering by customer ID and status. - operationId: listVerifications + Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer. + + These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations. + operationId: listPlatformExternalAccounts tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] parameters: - - name: customerId + - name: currency in: query - description: Filter by customer ID + description: Filter by currency code required: false schema: type: string - - name: verificationStatus - in: query - description: Filter by verification status - required: false - schema: - $ref: '#/components/schemas/VerificationStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2261,7 +2436,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/VerificationListResponse' + $ref: '#/components/schemas/ExternalAccountListResponse' '400': description: Bad request - Invalid parameters content: @@ -2280,94 +2455,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /verifications/{verificationId}: - get: - summary: Get a verification - description: Retrieve details of a specific verification by ID. - operationId: getVerification + post: + summary: Add a new platform external account + description: Register a new external bank account for the platform. + operationId: createPlatformExternalAccount tags: - - KYC/KYB Verifications + - External Accounts security: - BasicAuth: [] - parameters: - - name: verificationId - in: path - description: Verification ID - required: true - schema: - type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + platformAccountId: ext_acc_123456 + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: - '200': - description: Successful operation + '201': + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/Verification' + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Verification not found + '409': + description: Conflict - External account already exists content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-in: - post: - summary: Create a transfer-in request - description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. - operationId: createTransferIn + /platform/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string + get: + summary: Get platform external account by ID + description: Retrieve a platform external account by its system-generated ID + operationId: getPlatformExternalAccountById tags: - - Same-Currency Transfers + - External Accounts security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 responses: - '201': - description: Transfer-in request created successfully + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '400': - description: Bad request - Invalid parameters + $ref: '#/components/schemas/ExternalAccount' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + delete: + summary: Delete platform external account by ID + description: Delete a platform external account by its system-generated ID + operationId: deletePlatformExternalAccountById + tags: + - External Accounts + security: + - BasicAuth: [] + responses: + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -2375,7 +2589,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or account not found + description: External account not found content: application/json: schema: @@ -2386,49 +2600,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transfer-out: + /beneficial-owners: post: - summary: Create a transfer-out request + summary: Create a beneficial owner description: | - Transfer funds from an internal account to an external account for a specific customer. - operationId: createTransferOut + Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically. + operationId: createBeneficialOwner tags: - - Same-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - paymentRail: ACH - amount: 12550 - remittanceInformation: '12345' + $ref: '#/components/schemas/BeneficialOwnerCreateRequest' responses: '201': - description: Transfer-out request created successfully + description: Beneficial owner created successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/BeneficialOwner' '400': description: Bad request - Invalid parameters content: @@ -2442,7 +2636,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Customer or account not found + description: Customer not found content: application/json: schema: @@ -2453,45 +2647,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/uma/{receiverUmaAddress}: get: - summary: Look up an UMA address for payment + summary: List beneficial owners description: | - Lookup a receiving UMA address to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given UMA address. - operationId: lookupUma + Retrieve a list of beneficial owners for a business customer. + operationId: listBeneficialOwners tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: receiverUmaAddress - in: path - description: UMA address of the intended recipient + - name: customerId + in: query + description: The business customer ID required: true schema: type: string - - name: senderUmaAddress + - name: limit in: query - description: UMA address of the sender (optional if customerId is provided) + description: Maximum number of results to return (default 20, max 100) required: false schema: - type: string - - name: customerId + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor in: query - description: System ID of the sender (optional if senderUmaAddress is provided) + description: Cursor for pagination (returned from previous request) required: false schema: type: string responses: '200': - description: Successful lookup + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ReceiverUmaLookupResponse' + $ref: '#/components/schemas/BeneficialOwnerListResponse' '400': - description: Bad request - Missing or invalid parameters + description: Bad request - Invalid parameters content: application/json: schema: @@ -2502,74 +2697,35 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /receiver/external-account/{accountId}: + /beneficial-owners/{beneficialOwnerId}: get: - summary: Look up an external account for payment - description: | - Lookup an external account by ID to determine supported currencies and exchange rates. - This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. - operationId: lookupExternalAccount + summary: Get a beneficial owner + description: Retrieve details of a specific beneficial owner by ID. + operationId: getBeneficialOwner tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: accountId + - name: beneficialOwnerId in: path - description: System-generated ID of the external account + description: Beneficial owner ID required: true schema: type: string - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - - name: senderUmaAddress - in: query - description: UMA address of the sender (optional if customerId is provided) - required: false - schema: - type: string - - name: customerId - in: query - description: System ID of the sender (optional if senderUmaAddress is provided) - required: false - schema: - type: string responses: '200': - description: Successful lookup - content: - application/json: - schema: - $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' - '400': - description: Bad request - Missing or invalid parameters + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BeneficialOwner' '401': description: Unauthorized content: @@ -2577,55 +2733,51 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Beneficial owner not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue - content: - application/json: - schema: - $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes/{quoteId}: - get: - summary: Get quote by ID - description: | - Retrieve a quote by its ID. If the quote has been settled, it will include - the transaction ID. This allows clients to track the full lifecycle of a payment - from quote creation to settlement. - operationId: getQuoteById + patch: + summary: Update a beneficial owner + description: Update details of a specific beneficial owner. Only provided fields are updated. + operationId: updateBeneficialOwner tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] parameters: - - name: quoteId + - name: beneficialOwnerId in: path - description: ID of the quote to retrieve + description: Beneficial owner ID required: true schema: type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BeneficialOwnerUpdateRequest' responses: '200': - description: Quote retrieved successfully + description: Beneficial owner updated successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/BeneficialOwner' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -2633,7 +2785,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Beneficial owner not found content: application/json: schema: @@ -2644,125 +2796,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /quotes: + /documents: post: - summary: Create a transfer quote + summary: Upload a document description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. - - **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. - - **Account to UMA**: Transfer from an internal account to an UMA address. - - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - - **Key Features:** - - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount - - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + Upload a verification document for a customer or beneficial owner. The request must use multipart/form-data with the file in the `file` field and metadata in the remaining fields. - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. - operationId: createQuote + Supported file types: PDF, JPEG, PNG. Maximum file size: 10 MB. + operationId: uploadDocument tags: - - Cross-Currency Transfers + - Documents security: - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/QuoteRequest' - examples: - accountToAccount: - summary: Account to Account Transfer - value: - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - lockedCurrencySide: SENDING - lockedCurrencyAmount: 10000 - description: Transfer between accounts, either internal or external. - accountToUma: - summary: Account to UMA Address Transfer - value: - lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: UMA_ADDRESS - umaAddress: $receiver@uma.domain.com - currency: EUR - lockedCurrencySide: SENDING - lockedCurrencyAmount: 1000 - description: 'Payment for invoice #1234' - realTimeFundingToSparkWallet: - summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. - value: - source: - sourceType: REALTIME_FUNDING - customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 - currency: USD - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 - lockedCurrencySide: RECEIVING - lockedCurrencyAmount: 10000 - immediatelyExecute: true - description: Bitcoin reward payout! + $ref: '#/components/requestBodies/DocumentUploadRequestBody' responses: '201': - description: | - Transfer quote created successfully. The response includes exchange rates, - fees, and transfer details. For transfers involving UMA addresses, payment - instructions are also included for execution through banking systems. + description: Document uploaded successfully content: application/json: schema: - $ref: '#/components/schemas/Quote' - example: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - createdAt: '2025-10-03T12:00:00Z' - expiresAt: '2025-10-03T12:05:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: EUR - name: Euro - symbol: € - decimals: 2 - totalSendingAmount: 10000 - totalReceivingAmount: 9200 - exchangeRate: 0.92 - feesIncluded: 10 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 + $ref: '#/components/schemas/Document' '400': - description: Bad request - Missing or invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -2773,195 +2829,34 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Document holder not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /quotes/{quoteId}/execute: - post: - summary: Execute a quote + get: + summary: List documents description: | - Execute a quote by its ID. This endpoint initiates the transfer between - the source and destination accounts. - - This endpoint can only be used for quotes with a `source` which is either an internal account, - or has direct pull functionality (e.g. ACH pull with an external account). - - When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, - the request must include a `Grid-Wallet-Signature` header. The header value - is the full Grid wallet signature built over the `payloadToSign` value from - the quote's `paymentInstructions[].accountOrWalletInfo` entry with the - session private key of a verified authentication credential on the source - Embedded Wallet. - - Once executed, the quote cannot be cancelled and the transfer will be processed. - operationId: executeQuote - tags: - - Cross-Currency Transfers - security: - - BasicAuth: [] - parameters: - - name: quoteId - in: path - required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - responses: - '200': - description: | - Quote confirmed successfully. The transfer has been initiated and - the quote status has been updated. - content: - application/json: - schema: - $ref: '#/components/schemas/Quote' - '400': - description: Bad request - Invalid quote ID or quote cannot be confirmed - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Quote not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Quote already confirmed, expired, or in invalid state - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /transactions: - get: - summary: List transactions - description: | - Retrieve a paginated list of transactions with optional filtering. - The transactions can be filtered by customer ID, platform customer ID, UMA address, - date range, status, and transaction type. - - Card transactions are included and identified by `type: CARD`. In Sandbox this is how - you discover a `CardTransaction` id after simulating an authorization — list the - transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` - to the clearing and return simulate endpoints. - operationId: listTransactions + Retrieve a list of documents with optional filtering by document holder. + operationId: listDocuments tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: customerId - in: query - description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. - required: false - schema: - type: string - - name: platformCustomerId - in: query - description: Filter by platform-specific customer ID - required: false - schema: - type: string - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate + - name: documentHolder in: query - description: Filter by end date (inclusive) in ISO 8601 format + description: Filter by document holder ID (Customer or BeneficialOwner) required: false schema: type: string - format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -2977,23 +2872,13 @@ paths: required: false schema: type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/DocumentListResponse' '400': description: Bad request - Invalid parameters content: @@ -3012,29 +2897,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string + /documents/{documentId}: get: - summary: Get transaction by ID - description: Retrieve detailed information about a specific transaction. - operationId: getTransactionById + summary: Get a document by ID + description: Retrieve details and metadata of a specific document by ID. + operationId: getDocument tags: - - Transactions + - Documents security: - BasicAuth: [] + parameters: + - name: documentId + in: path + description: Document ID + required: true + schema: + type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Document' '401': description: Unauthorized content: @@ -3042,7 +2927,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: @@ -3053,38 +2938,33 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/confirm: - post: - summary: Confirm receipt delivery + put: + summary: Replace a document description: | - Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. - operationId: confirmReceiptDelivery + Replace an existing document with a new file and/or updated metadata. This is useful when a document was rejected and needs to be re-uploaded. The request must use multipart/form-data. + operationId: replaceDocument tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: transactionId + - name: documentId in: path - description: Unique identifier of the transaction to confirm receipt delivery for + description: Document ID required: true schema: type: string requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' + $ref: '#/components/requestBodies/DocumentReplaceRequestBody' responses: '200': - description: Receipt delivery confirmation recorded successfully + description: Document replaced successfully content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' + $ref: '#/components/schemas/Document' '400': - description: Bad request - Invalid parameters + description: Bad request - Invalid file type, size, or parameters content: application/json: schema: @@ -3096,7 +2976,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: @@ -3107,43 +2987,25 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/approve: - post: - summary: Approve a pending incoming payment + delete: + summary: Delete a document description: | - Approve a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously approve payments after async processing. - operationId: approvePendingPayment + Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable. + operationId: deleteDocument tags: - - Transactions + - Documents security: - BasicAuth: [] parameters: - - name: transactionId + - name: documentId in: path - description: Unique identifier of the transaction to approve + description: Document ID required: true schema: type: string - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/ApprovePaymentRequest' responses: - '200': - description: Payment approved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - Invalid parameters or payment cannot be approved - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Document deleted successfully '401': description: Unauthorized content: @@ -3151,13 +3013,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Document not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. + description: Conflict - Document cannot be deleted (already submitted for verification) content: application/json: schema: @@ -3168,39 +3030,106 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /transactions/{transactionId}/reject: + /verifications: post: - summary: Reject a pending incoming payment + summary: Submit customer for verification description: | - Reject a pending incoming payment that was previously acknowledged with a 202 response. - This endpoint allows platforms to asynchronously reject payments after additional processing. - operationId: rejectPendingPayment + Trigger KYC (individual) or KYB (business) verification for a customer. + The response indicates whether all required information has been provided. + If data is missing, the `errors` array describes exactly what needs to be + supplied before verification can proceed. + + Call this endpoint again after resolving errors to re-submit. + + ### What to collect for KYB + + Before submitting a `BUSINESS` customer, collect the following via + `POST /customers`, `POST /beneficial-owners`, and `POST /documents`: + + **Business identifying information** + - Entity full legal name + - Doing Business As (DBA) name, if applicable + - Physical address — principal place of business + - Countries of operation + - Identification number — U.S. taxpayer identification number, or, for a + foreign business without one, alternative government-issued documentation + certifying the existence of the business + + **Ownership and control structure** — collected for **one control person** + (an individual with significant responsibility to control, manage, or + direct the legal entity) **and all beneficial owners** (every individual + who owns 25% or more, directly or indirectly). For each, provide: + - Full name + - Date of birth + - Address + - Identification number: + - U.S. persons — SSN or ITIN + - Non-U.S. persons — one or more of: ITIN, passport (with country of + issuance), alien identification card, or another government-issued + photo ID evidencing nationality or residence + + **Required documents** + - Company formation and existence documents (certificate of incorporation, + articles of association, etc.) + - Proof of ownership and control structure (organization and ownership + chart, shareholder agreements, operating agreements, register of members, + or certification of controlling person and beneficial owners) + - Proof of address dated within the last 3 months (utility bill, bank + statement, lease agreement, or official correspondence) + - Tax ID or equivalent identifying-number documents + - For non-U.S. beneficial owners — passport plus one additional + government-issued ID + operationId: createVerification tags: - - Transactions + - KYC/KYB Verifications security: - BasicAuth: [] - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction to reject - required: true - schema: - type: string requestBody: - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/RejectPaymentRequest' + $ref: '#/components/schemas/VerificationRequest' responses: '200': - description: Payment rejected successfully + description: | + Verification status returned. Check `verificationStatus` and `errors` to determine next steps. content: application/json: schema: - $ref: '#/components/schemas/IncomingTransaction' + $ref: '#/components/schemas/Verification' + examples: + missingInfo: + summary: Verification blocked by missing data + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000001 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_FIELD + field: customer.address.line1 + reason: Business address line 1 is required + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002 + type: MISSING_FIELD + field: personalInfo.birthDate + reason: Date of birth is required for beneficial owners + createdAt: '2025-10-03T12:00:00Z' + submitted: + summary: Verification submitted successfully + value: + id: Verification:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: IN_PROGRESS + errors: [] + createdAt: '2025-10-03T12:00:00Z' '400': - description: Bad request - Invalid parameters or payment cannot be rejected + description: Bad request - Invalid parameters content: application/json: schema: @@ -3212,55 +3141,61 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Transaction not found + description: Customer not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Payment is not in a pending state or has already been processed or timed out. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /crypto/estimate-withdrawal-fee: - post: - summary: Estimate crypto withdrawal fee + get: + summary: List verifications description: | - Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. - operationId: estimateCryptoWithdrawalFee + Retrieve a list of verifications with optional filtering by customer ID and status. + operationId: listVerifications tags: - - Cross-Currency Transfers + - KYC/KYB Verifications security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' - examples: - estimateUSDCWithdrawal: - summary: Estimate fee for USDC withdrawal on Solana - value: - internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - currency: USDC - cryptoNetwork: SOLANA - amount: 1000000 - destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU + parameters: + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: verificationStatus + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/VerificationStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': - description: Fee estimation returned successfully + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' + $ref: '#/components/schemas/VerificationListResponse' '400': description: Bad request - Invalid parameters content: @@ -3273,8 +3208,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /verifications/{verificationId}: + get: + summary: Get a verification + description: Retrieve details of a specific verification by ID. + operationId: getVerification + tags: + - KYC/KYB Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Verification ID + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Verification' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Verification not found content: application/json: schema: @@ -3285,24 +3255,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/webhooks/test: + /transfer-in: post: - summary: Send a test webhook - description: Send a test webhook to the configured endpoint - operationId: sendTestWebhook + summary: Create a transfer-in request + description: | + Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + operationId: createTransferIn tags: - - Sandbox + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: - '200': - description: Webhook delivered successfully + '201': + description: Transfer-in request created successfully content: application/json: schema: - $ref: '#/components/schemas/TestWebhookResponse' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request - Webhook delivery failed + description: Bad request - Invalid parameters content: application/json: schema: @@ -3313,133 +3308,129 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/csv: + /transfer-out: post: - summary: Upload customers via CSV file + summary: Create a transfer-out request description: | - Upload a CSV file containing customer information for bulk creation. The CSV file should follow - a specific format with required and optional columns based on customer type. - - ### CSV Format - The CSV file should have the following columns: - - Required columns for all customers: - - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) - - platformCustomerId: Your platform's unique identifier for the customer - - customerType: Either "INDIVIDUAL" or "BUSINESS" - - Required columns for individual customers: - - fullName: Individual's full name - - birthDate: Date of birth in YYYY-MM-DD format - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Required columns for business customers: - - businessLegalName: Legal name of the business - - addressLine1: Street address line 1 - - city: City - - state: State/Province/Region - - postalCode: Postal/ZIP code - - country: Country code (ISO 3166-1 alpha-2) - - Optional columns for all customers: - - addressLine2: Street address line 2 - - platformAccountId: Your platform's identifier for the bank account - - description: Optional description for the customer - - Optional columns for individual customers: - - email: Customer's email address - - Optional columns for business customers: - - businessRegistrationNumber: Business registration number - - businessTaxId: Tax identification number - - ### Example CSV - ```csv - umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName - john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US - acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US - ``` - - The upload process is asynchronous and will return a job ID that can be used to track progress. - You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. - operationId: uploadCustomersCsv + Transfer funds from an internal account to an external account for a specific customer. + operationId: createTransferOut tags: - - Customers + - Same-Currency Transfers security: - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: - multipart/form-data: + application/json: schema: - type: object - required: - - file - properties: - file: - type: string - format: binary - description: CSV file containing customer information + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + amount: 12550 + remittanceInformation: '12345' responses: - '202': - description: CSV upload accepted for processing + '201': + description: | + Transfer-out request created successfully. content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJobAccepted' + $ref: '#/components/schemas/TransactionOneOf' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Customer or account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/bulk/jobs/{jobId}: + /receiver/uma/{receiverUmaAddress}: get: - summary: Get bulk import job status + summary: Look up an UMA address for payment description: | - Retrieve the current status and results of a bulk customer import job. This endpoint can be used - to track the progress of both CSV uploads. - - The response includes: - - Overall job status - - Progress statistics - - Detailed error information for failed entries - - Completion timestamp when finished - operationId: getBulkCustomerImportJob + Lookup a receiving UMA address to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given UMA address. + operationId: lookupUma tags: - - Customers + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: jobId + - name: receiverUmaAddress in: path - description: ID of the bulk import job to retrieve + description: UMA address of the intended recipient required: true schema: type: string + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: '200': - description: Job status retrieved successfully + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/BulkCustomerImportJob' + $ref: '#/components/schemas/ReceiverUmaLookupResponse' + '400': + description: Bad request - Missing or invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3447,42 +3438,69 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Job not found + description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`). content: application/json: schema: $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations: - post: - summary: Create an UMA invitation + /receiver/external-account/{accountId}: + get: + summary: Look up an external account for payment description: | - Create an UMA invitation from a given platform customer. - operationId: createInvitation + Lookup an external account by ID to determine supported currencies and exchange rates. + This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent. + operationId: lookupExternalAccount tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UmaInvitationCreateRequest' + parameters: + - name: accountId + in: path + description: System-generated ID of the external account + required: true + schema: + type: string + example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + - name: senderUmaAddress + in: query + description: UMA address of the sender (optional if customerId is provided) + required: false + schema: + type: string + - name: customerId + in: query + description: System ID of the sender (optional if senderUmaAddress is provided) + required: false + schema: + type: string responses: - '201': - description: Invitation created successfully + '200': + description: Successful lookup content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -3493,36 +3511,56 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '412': + description: Counterparty doesn't support UMA version + content: + application/json: + schema: + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}: + /quotes/{quoteId}: get: - summary: Get an UMA invitation by code + summary: Get quote by ID description: | - Retrieve details about an UMA invitation by its invitation code. - operationId: getInvitation + Retrieve a quote by its ID. If the quote has been settled, it will include + the transaction ID. This allows clients to track the full lifecycle of a payment + from quote creation to settlement. + operationId: getQuoteById tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode + - name: quoteId in: path - description: The code of the invitation to get + description: ID of the quote to retrieve required: true schema: type: string responses: '200': - description: Invitation retrieved successfully + description: Quote retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' '401': description: Unauthorized content: @@ -3530,7 +3568,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Invitation not found + description: Quote not found content: application/json: schema: @@ -3541,45 +3579,133 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/claim: + /quotes: post: - summary: Claim an UMA invitation + summary: Create a transfer quote description: | - Claim an UMA invitation by associating it with an invitee UMA address. + Generate a quote for a cross-currency transfer between any combination of accounts + and UMA addresses. This endpoint handles currency exchange and provides the necessary + instructions to execute the transfer. - When an invitation is successfully claimed: - 1. The invitation status changes from PENDING to CLAIMED - 2. The invitee UMA address is associated with the invitation - 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation + **Transfer Types Supported:** + - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to UMA**: Transfer from an internal account to an UMA address. + - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. - This endpoint allows customers to accept invitations sent to them by other UMA customers. - operationId: claimInvitation + **Key Features:** + - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount + - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates + - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution + + **Important:** If you are transferring funds in the same currency (no exchange required), + use the `/transfer-in` or `/transfer-out` endpoints instead. + operationId: createQuote tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode - in: path - description: The code of the invitation to claim - required: true + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UmaInvitationClaimRequest' + $ref: '#/components/schemas/QuoteRequest' + examples: + accountToAccount: + summary: Account to Account Transfer + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + lockedCurrencySide: SENDING + lockedCurrencyAmount: 10000 + description: Transfer between accounts, either internal or external. + accountToUma: + summary: Account to UMA Address Transfer + value: + lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009 + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: UMA_ADDRESS + umaAddress: $receiver@uma.domain.com + currency: EUR + lockedCurrencySide: SENDING + lockedCurrencyAmount: 1000 + description: 'Payment for invoice #1234' + realTimeFundingToSparkWallet: + summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution. + value: + source: + sourceType: REALTIME_FUNDING + customerId: Customer:019542f5-b3e7-1d02-0000-000000000009 + currency: USD + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456 + lockedCurrencySide: RECEIVING + lockedCurrencyAmount: 10000 + immediatelyExecute: true + description: Bitcoin reward payout! responses: - '200': - description: Invitation claimed successfully + '201': + description: | + Transfer quote created successfully. The response includes exchange rates, + fees, and transfer details. For transfers involving UMA addresses, payment + instructions are also included for execution through banking systems. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' + example: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + createdAt: '2025-10-03T12:00:00Z' + expiresAt: '2025-10-03T12:05:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: EUR + name: Euro + symbol: € + decimals: 2 + totalSendingAmount: 10000 + totalReceivingAmount: 9200 + exchangeRate: 0.92 + feesIncluded: 10 + platformFeesIncluded: 0 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005 + '202': + description: | + Quote created but awaiting Strong Customer Authentication. Returned only for customers whose provider requires SCA (e.g. EU) on a realtime-funding source: the quote has status `PENDING_AUTHORIZATION` and carries an `scaChallenge`, and `paymentInstructions` are **withheld** until the challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once authorized, the quote's `paymentInstructions` are populated. + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -3590,89 +3716,155 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Invitation not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /invitations/{invitationCode}/cancel: + '501': + description: Not implemented + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /quotes/{quoteId}/execute: post: - summary: Cancel an UMA invitation + summary: Execute a quote description: | - Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. + Execute a quote by its ID. This endpoint initiates the transfer between + the source and destination accounts. - When an invitation is cancelled: - 1. The invitation status changes from PENDING to CANCELLED - 2. The invitation can no longer be claimed - 3. The invitation URL will show as cancelled when accessed + This endpoint can only be used for quotes with a `source` which is either an internal account, + or has direct pull functionality (e.g. ACH pull with an external account). - Only pending invitations can be cancelled. Attempting to cancel an invitation - that is already claimed, expired, or cancelled will result in an error. - operationId: cancelInvitation + When the quote's `source` is an internal account of type `EMBEDDED_WALLET`, + the request must include a `Grid-Wallet-Signature` header. The header value + is the full Grid wallet signature built over the `payloadToSign` value from + the quote's `paymentInstructions[].accountOrWalletInfo` entry with the + session private key of a verified authentication credential on the source + Embedded Wallet. + + Once executed, the quote cannot be cancelled and the transfer will be processed. + operationId: executeQuote tags: - - Invitations + - Cross-Currency Transfers security: - BasicAuth: [] parameters: - - name: invitationCode + - name: quoteId in: path - description: The code of the invitation to cancel required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + schema: + type: string + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. schema: type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteQuoteRequest' responses: '200': - description: Invitation cancelled successfully + description: | + Quote processed. The outcome depends on whether SCA applies to the customer (currently EU customers): + + - **No SCA required:** the transfer is initiated and the quote status advances (`PROCESSING` / `COMPLETED`). + + - **SCA required:** the transfer is **not** initiated yet. The quote is returned with status `PENDING_AUTHORIZATION` and an `scaChallenge`; release the transfer by authorizing the quote you already hold — `POST /quotes/{quoteId}/authorize` (re-calling `execute` returns 409). A pre-funded send is a single challenge, so one authorization releases it. The challenge (and its SMS code / passkey assertion) only comes into existence once this call initiates the transfer, so the proof is always supplied on the follow-up authorize, never inline on this call. If an SMS code lapses, re-send it via `POST /quotes/{quoteId}/authorize/resend`. content: application/json: schema: - $ref: '#/components/schemas/UmaInvitation' + $ref: '#/components/schemas/Quote' '400': - description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) + description: Bad request - Invalid quote ID or quote cannot be confirmed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Only the platform which created the invitation can cancel it + '404': + description: Quote not found content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Invitation not found + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Quote already confirmed, expired, or in invalid state content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/send: + /quotes/{quoteId}/authorize: + parameters: + - name: quoteId + in: path + description: The unique identifier of the quote whose SCA challenge is being authorized. + required: true + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000006 post: - summary: Simulate sending funds + summary: Authorize a quote's SCA challenge description: | - Simulate sending funds to the bank account as instructed in the quote. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxSend + Satisfy the Strong Customer Authentication challenge carried by a quote in + `PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof. + + This is used for realtime-funding quotes: the quote is returned with an + `scaChallenge` and **without** `paymentInstructions`; once authorized, the + quote advances and its `paymentInstructions` are populated so the customer + can fund the transfer. + + As with all SCA, a quote may require more than one authorization: after + authorizing, if the quote is still `PENDING_AUTHORIZATION` it carries the + next `scaChallenge` — authorize that too, repeating until it advances (see + `ScaChallenge`). + + This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`. + + In sandbox, the SMS code is always `123456`. + operationId: authorizeQuote tags: - - Sandbox + - Strong Customer Authentication security: - BasicAuth: [] requestBody: @@ -3680,16 +3872,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SandboxSendRequest' + $ref: '#/components/schemas/ScaAuthorization' responses: '200': - description: Funds received successfully + description: Challenge authorized; the updated quote is returned. content: application/json: schema: - $ref: '#/components/schemas/OutgoingTransaction' + $ref: '#/components/schemas/Quote' '400': - description: Bad request + description: Invalid or expired authorization proof content: application/json: schema: @@ -3700,178 +3892,176 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: SCA is not required for this customer, or the quote has no pending challenge to authorize. Also returned with `SCA_SESSION_REQUIRED` when the customer's SCA login session is missing or expired — re-authenticate the customer, then authorize again. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '423': + description: Returned with `ACCOUNT_LOCKED` when the customer's login is temporarily locked after too many failed authorization attempts. The customer must wait for the lock to expire before authorizing again. + content: + application/json: + schema: + $ref: '#/components/schemas/Error423' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when authorization attempts for this challenge happen too frequently (for example, repeated bad codes brute-forcing the OTP). The challenge may be invalidated after too many failed attempts. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/uma/receive: + /quotes/{quoteId}/authorize/resend: + parameters: + - name: quoteId + in: path + description: The unique identifier of the quote whose SCA challenge code should be re-sent. + required: true + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000006 post: - summary: Simulate payment send to test receiving an UMA payment + summary: Resend a quote's SCA challenge code description: | - Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxReceive - tags: - - Sandbox + Re-send the one-time code for a realtime-funding quote in + `PENDING_AUTHORIZATION` status whose `scaChallenge.factor` is `SMS_OTP`. The + existing challenge is reused — no new challenge is issued, and its + `scaChallenge.expiresAt` is **not** extended; once the challenge is past + `expiresAt` it can no longer be authorized. + + Only meaningful for customers in a region where SCA is required (e.g. EU); + a 409 is returned otherwise. `PASSKEY` challenges cannot be re-sent and return 409. + + In sandbox, the code is always `123456`. + operationId: resendQuoteScaCode + tags: + - Strong Customer Authentication security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '200': - description: Payment triggered successfully - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingTransaction' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + '204': + description: Code re-sent. '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Sender or receiver not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /sandbox/internal-accounts/{accountId}/fund: - post: - summary: Simulate funding an internal account - description: | - Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. - This endpoint is only for the sandbox environment and will fail for production platforms/keys. - operationId: sandboxFundInternalAccount - tags: - - Sandbox - security: - - BasicAuth: [] - parameters: - - name: accountId - in: path - required: true - description: The ID of the internal account to fund - schema: - type: string - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxFundRequest' - examples: - fundUSDAccount: - summary: Fund USD internal account with $1,000 - value: - amount: 100000 - fundBTCAccount: - summary: Fund BTC internal account with 0.01 BTC - value: - amount: 1000000 - responses: - '200': - description: Internal account funded successfully - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token + '409': + description: SCA is not required for this customer, the quote has no pending SMS challenge, or the challenge uses a factor whose code cannot be re-sent (e.g. passkey). content: application/json: schema: - $ref: '#/components/schemas/Error403' - '404': - description: Internal account not found + $ref: '#/components/schemas/Error409' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when codes are re-sent too frequently, to avoid spamming the customer's phone. Clients should back off and retry after the interval indicated by the `Retry-After` response header. content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /uma-providers: + /transactions: get: - summary: List available Counterparty Providers + summary: List transactions description: | - Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. - operationId: getAvailableUmaProviders + Retrieve a paginated list of transactions with optional filtering. + The transactions can be filtered by customer ID, platform customer ID, UMA address, + date range, status, and transaction type. + + Card transactions are included and identified by `type: CARD`. In Sandbox this is how + you discover a `CardTransaction` id after simulating an authorization — list the + transactions, take the card transaction's `id`, and pass it as the `cardTransactionId` + to the clearing and return simulate endpoints. + operationId: listTransactions tags: - - Available UMA Providers + - Transactions + security: + - BasicAuth: [] parameters: - - in: query - name: countryCode - description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. + - name: customerId + in: query + description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID. required: false schema: type: string - example: US - - in: query - name: currencyCode - description: The ISO 4217 currency code to filter providers by supported currency. + - name: platformCustomerId + in: query + description: Filter by platform-specific customer ID required: false schema: type: string - example: USD - - in: query - name: hasBlockedProviders - description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) required: false schema: - type: boolean + type: string + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -3897,15 +4087,19 @@ paths: - asc - desc default: desc - security: - - BasicAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/UmaProviderListResponse' + $ref: '#/components/schemas/TransactionListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -3918,113 +4112,77 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens: - post: - summary: Create a new API token - description: Create a new API token to access the Grid APIs. - operationId: createToken + /transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get transaction by ID + description: Retrieve detailed information about a specific transaction. + operationId: getTransactionById tags: - - API Tokens + - Transactions security: - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '201': - description: API token created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ApiToken' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List tokens + /transactions/{transactionId}/confirm: + post: + summary: Confirm receipt delivery description: | - Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match - the specified filters. If no filters are provided, returns all tokens (paginated). - operationId: listTokens + Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time. + operationId: confirmReceiptDelivery tags: - - API Tokens + - Transactions security: - BasicAuth: [] parameters: - - name: name - in: query - description: Filter by name of the token - required: false - schema: - type: string - - name: createdAfter - in: query - description: Filter customers created after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: createdBefore - in: query - description: Filter customers created before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedAfter - in: query - description: Filter customers updated after this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: updatedBefore - in: query - description: Filter customers updated before this timestamp (inclusive) - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false + - name: transactionId + in: path + description: Unique identifier of the transaction to confirm receipt delivery for + required: true schema: type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest' responses: '200': - description: Successful operation + description: Receipt delivery confirmation recorded successfully content: application/json: schema: - $ref: '#/components/schemas/TokenListResponse' + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request - Invalid parameters content: @@ -4037,35 +4195,55 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /tokens/{tokenId}: - parameters: - - name: tokenId - in: path - description: System-generated unique token identifier - required: true - schema: - type: string - get: - summary: Get API token by ID - description: Retrieve an API token by their system-generated ID - operationId: getTokenById + /transactions/{transactionId}/approve: + post: + summary: Approve a pending incoming payment + description: | + Approve a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously approve payments after async processing. + operationId: approvePendingPayment tags: - - API Tokens + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to approve + required: true + schema: + type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/ApprovePaymentRequest' responses: '200': - description: Successful operation + description: Payment approved successfully content: application/json: schema: - $ref: '#/components/schemas/ApiToken' + $ref: '#/components/schemas/IncomingTransaction' + '400': + description: Bad request - Invalid parameters or payment cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -4073,30 +4251,56 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Token not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - delete: - summary: Delete API token by ID - description: Delete an API token by their system-generated ID - operationId: deleteTokenById + /transactions/{transactionId}/reject: + post: + summary: Reject a pending incoming payment + description: | + Reject a pending incoming payment that was previously acknowledged with a 202 response. + This endpoint allows platforms to asynchronously reject payments after additional processing. + operationId: rejectPendingPayment tags: - - API Tokens + - Transactions security: - BasicAuth: [] + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction to reject + required: true + schema: + type: string + requestBody: + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/RejectPaymentRequest' responses: - '204': - description: API token deleted successfully + '200': + description: Payment rejected successfully + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingTransaction' '400': - description: Bad request + description: Bad request - Invalid parameters or payment cannot be rejected content: application/json: schema: @@ -4108,208 +4312,124 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Token not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Payment is not in a pending state or has already been processed or timed out. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}: - parameters: - - name: id - in: path - description: The id of the internal account to update. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - patch: - summary: Update internal account + /transactions/{transactionId}/cancel: + post: + summary: Cancel a bank transfer description: | - Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - - Updating wallet privacy is a two-step signed-retry flow: - - 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. - operationId: updateInternalAccount + Request cancellation of a pending bank transfer — an ACH transfer (push or pull) or a wire — before it has settled, for example a payment or collection initiated outside of the receiving bank's processing window. + Whether a transfer can still be cancelled is determined by the banking partner that is settling it: the request is forwarded to the partner's own cancellation facility, and a transfer that the partner has already processed (or that is otherwise past its cancellation window) cannot be cancelled. Cancellation applies to bank-rail transfers; requests for transaction types that cannot be cancelled are rejected. + operationId: cancelTransaction tags: - - Internal Accounts + - Transactions security: - BasicAuth: [] parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + - name: transactionId + in: path + description: Unique identifier of the transaction to cancel + required: true schema: type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/InternalAccountUpdateRequest' - examples: - updateWalletPrivacy: - summary: Update wallet privacy request (both steps) - value: - privateEnabled: true + $ref: '#/components/schemas/CancelTransactionRequest' responses: '200': - description: Signed retry accepted. Returns the updated internal account. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccount' - examples: - enabled: - summary: Wallet privacy enabled - value: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: EMBEDDED_WALLET - status: ACTIVE - balance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - privateEnabled: true - createdAt: '2026-04-08T15:30:00Z' - updatedAt: '2026-04-08T15:35:02Z' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Cancellation requested successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Internal account update challenge - value: - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/TransactionOneOf' '400': - description: Bad request + description: Bad request - Invalid parameters or the transaction cannot be cancelled content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Internal account not found + description: Transaction not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '409': + description: Conflict - The transaction has already settled or is otherwise past the point where it can be cancelled. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /internal-accounts/{id}/export: + /crypto/estimate-withdrawal-fee: post: - summary: Export internal account wallet credentials + summary: Estimate crypto withdrawal fee description: | - Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. - - Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): - - 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. - - The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. - operationId: exportInternalAccount + Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal. + operationId: estimateCryptoWithdrawalFee tags: - - Internal Accounts + - Cross-Currency Transfers security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the internal account to export. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountExportRequest' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest' examples: - export: - summary: Export request (both steps) + estimateUSDCWithdrawal: + summary: Estimate fee for USDC withdrawal on Solana value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + currency: USDC + cryptoNetwork: SOLANA + amount: 1000000 + destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU responses: '200': - description: Signed retry accepted. Returns the encrypted wallet credentials. - content: - application/json: - schema: - $ref: '#/components/schemas/InternalAccountExportResponse' - '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Fee estimation returned successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: @@ -4326,237 +4446,118 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials: + /sandbox/webhooks/test: post: - summary: Create an authentication credential - description: | - Register an authentication credential for an Embedded Wallet customer. - - Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. - - Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. - operationId: createAuthCredential + summary: Send a test webhook + description: Send a test webhook to the configured endpoint + operationId: sendTestWebhook tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' - examples: - emailOtp: - summary: Add an email OTP credential - value: - type: EMAIL_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - smsOtp: - summary: Add an SMS OTP credential - value: - type: SMS_OTP - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oauth: - summary: Add an OAuth credential - value: - type: OAUTH - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - passkey: - summary: Add a passkey credential - value: - type: PASSKEY - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: iPhone Face-ID - challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx - attestation: - credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY - clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 - attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow - transports: - - internal - - hybrid responses: - '201': - description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthMethodResponse' - examples: - emailOtp: - summary: Email OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - smsOtp: - summary: SMS OTP credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: SMS_OTP - nickname: '+14155550123' - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - oauth: - summary: OAuth credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - passkey: - summary: Passkey credential created - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - '202': - description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. + '200': + description: Webhook delivered successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Additional email OTP credential challenge - value: - type: EMAIL_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - smsOtp: - summary: Additional SMS OTP credential challenge - value: - type: SMS_OTP - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - oauth: - summary: Additional OAuth credential challenge - value: - type: OAUTH - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' - passkey: - summary: Additional passkey credential challenge - value: - type: PASSKEY - payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/TestWebhookResponse' '400': - description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. + description: Bad request - Webhook delivery failed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Internal account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - get: - summary: List authentication credentials - description: |- - Retrieve all authentication credentials registered on an Embedded Wallet internal account. + /customers/bulk/csv: + post: + summary: Upload customers via CSV file + description: | + Upload a CSV file containing customer information for bulk creation. The CSV file should follow + a specific format with required and optional columns based on customer type. - The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. - operationId: listAuthCredentials - tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: accountId - in: query - description: Internal account id whose authentication credentials to list. - required: true - schema: - type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - responses: - '200': - description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthCredentialListResponse' - examples: - multipleCredentials: - summary: Internal account with multiple authentication credentials - value: - data: - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:01Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: OAUTH - nickname: example@lightspark.com - createdAt: '2026-04-08T15:35:00Z' - updatedAt: '2026-04-08T15:35:00Z' - - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-09T10:15:00Z' - updatedAt: '2026-04-09T10:15:00Z' - empty: - summary: No credentials registered on the account - value: - data: [] - '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + ### CSV Format + The CSV file should have the following columns: + + Required columns for all customers: + - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com) + - platformCustomerId: Your platform's unique identifier for the customer + - customerType: Either "INDIVIDUAL" or "BUSINESS" + + Required columns for individual customers: + - fullName: Individual's full name + - birthDate: Date of birth in YYYY-MM-DD format + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Required columns for business customers: + - businessLegalName: Legal name of the business + - addressLine1: Street address line 1 + - city: City + - state: State/Province/Region + - postalCode: Postal/ZIP code + - country: Country code (ISO 3166-1 alpha-2) + + Optional columns for all customers: + - addressLine2: Street address line 2 + - platformAccountId: Your platform's identifier for the bank account + - description: Optional description for the customer + + Optional columns for individual customers: + - email: Customer's email address + + Optional columns for business customers: + - businessRegistrationNumber: Business registration number + - businessTaxId: Tax identification number + + ### Example CSV + ```csv + umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName + john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US + acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US + ``` + + The upload process is asynchronous and will return a job ID that can be used to track progress. + You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint. + operationId: uploadCustomersCsv + tags: + - Customers + security: + - BasicAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - file + properties: + file: + type: string + format: binary + description: CSV file containing customer information + responses: + '202': + description: CSV upload accepted for processing content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJobAccepted' '401': description: Unauthorized content: @@ -4569,68 +4570,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}: - delete: - summary: Revoke an authentication credential + /customers/bulk/jobs/{jobId}: + get: + summary: Get bulk import job status description: | - Revoke an authentication credential on an Embedded Wallet internal account. - - Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: - - 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + Retrieve the current status and results of a bulk customer import job. This endpoint can be used + to track the progress of both CSV uploads. - The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. - operationId: revokeAuthCredential + The response includes: + - Overall job status + - Progress statistics + - Detailed error information for failed entries + - Completion timestamp when finished + operationId: getBulkCustomerImportJob tags: - - Embedded Wallet Auth + - Customers security: - BasicAuth: [] parameters: - - name: id + - name: jobId in: path - description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + description: ID of the bulk import job to retrieve required: true schema: type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Authentication credential revoked successfully. - '400': - description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + '200': + description: Job status retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/BulkCustomerImportJob' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Job not found content: application/json: schema: @@ -4641,103 +4619,29 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/verify: + /invitations: post: - summary: Verify an authentication credential + summary: Create an UMA invitation description: | - Complete the verification step for a previously created authentication credential and issue a session. - - For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. - In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. - - For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. - - On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. - operationId: verifyAuthCredential + Create an UMA invitation from a given platform customer. + operationId: createInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' - examples: - emailOtp: - summary: Verify an email OTP credential (first leg) - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - smsOtp: - summary: Verify an SMS OTP credential (first leg) - value: - type: SMS_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - emailOtpSignedRetry: - summary: Signed retry completing an email OTP verification - description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. - value: - type: EMAIL_OTP - encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' - oauth: - summary: Verify an OAuth credential - value: - type: OAUTH - oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - passkey: - summary: Verify a passkey credential - value: - type: PASSKEY - assertion: - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 - authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA - signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 + $ref: '#/components/schemas/UmaInvitationCreateRequest' responses: - '200': - description: Authentication credential verified and session issued - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - '202': - description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + '201': + description: Invitation created successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - examples: - emailOtp: - summary: Email OTP verification challenge (sign and retry) - value: - type: EMAIL_OTP - payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/UmaInvitation' '400': description: Bad request content: @@ -4745,106 +4649,96 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Authentication credential not found + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. + $ref: '#/components/schemas/Error500' + /invitations/{invitationCode}: + get: + summary: Get an UMA invitation by code + description: | + Retrieve details about an UMA invitation by its invitation code. + operationId: getInvitation + tags: + - Invitations + security: + - BasicAuth: [] + parameters: + - name: invitationCode + in: path + description: The code of the invitation to get + required: true + schema: + type: string + responses: + '200': + description: Invitation retrieved successfully + content: + application/json: schema: - type: integer + $ref: '#/components/schemas/UmaInvitation' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error429' + $ref: '#/components/schemas/Error401' + '404': + description: Invitation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/credentials/{id}/challenge: + /invitations/{invitationCode}/claim: post: - summary: Re-issue an authentication credential challenge + summary: Claim an UMA invitation description: | - Re-issue the challenge for an existing authentication credential. - - For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. + Claim an UMA invitation by associating it with an invitee UMA address. - `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + When an invitation is successfully claimed: + 1. The invitation status changes from PENDING to CLAIMED + 2. The invitee UMA address is associated with the invitation + 3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation - For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. - operationId: challengeAuthCredential + This endpoint allows customers to accept invitations sent to them by other UMA customers. + operationId: claimInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] parameters: - - name: id + - name: invitationCode in: path - description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + description: The code of the invitation to claim required: true schema: type: string requestBody: - description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. - required: false + required: true content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialChallengeRequest' - examples: - passkey: - summary: Re-challenge a passkey credential - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 - emailOtp: - summary: Re-challenge an email-OTP credential (empty body) - value: {} - smsOtp: - summary: Re-challenge an SMS-OTP credential (empty body) - value: {} + $ref: '#/components/schemas/UmaInvitationClaimRequest' responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Invitation claimed successfully content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' - examples: - emailOtp: - summary: Email OTP challenge re-issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - nickname: example@lightspark.com - otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - passkey: - summary: Passkey reauthentication challenge issued - value: - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: PASSKEY - credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew - nickname: iPhone Face-ID - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 - requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/UmaInvitation' '400': description: Bad request content: @@ -4858,57 +4752,51 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Authentication credential not found + description: Invitation not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '429': - description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. - headers: - Retry-After: - description: Number of seconds to wait before retrying the request. - schema: - type: integer - content: - application/json: - schema: - $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions: - get: - summary: List active sessions - description: |- - Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. + /invitations/{invitationCode}/cancel: + post: + summary: Cancel an UMA invitation + description: | + Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation. - The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. - operationId: listAuthSessions + When an invitation is cancelled: + 1. The invitation status changes from PENDING to CANCELLED + 2. The invitation can no longer be claimed + 3. The invitation URL will show as cancelled when accessed + + Only pending invitations can be cancelled. Attempting to cancel an invitation + that is already claimed, expired, or cancelled will result in an error. + operationId: cancelInvitation tags: - - Embedded Wallet Auth + - Invitations security: - BasicAuth: [] parameters: - - name: accountId - in: query - description: Internal account id whose sessions to list. + - name: invitationCode + in: path + description: The code of the invitation to cancel required: true schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. + description: Invitation cancelled successfully content: application/json: schema: - $ref: '#/components/schemas/SessionListResponse' + $ref: '#/components/schemas/UmaInvitation' '400': - description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. + description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled) content: application/json: schema: @@ -4919,58 +4807,48 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Only the platform which created the invitation can cancel it + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Invitation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}: - delete: - summary: Revoke an authentication session + /sandbox/send: + post: + summary: Simulate sending funds description: | - Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: - - 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. - - Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. - operationId: revokeAuthSession + Simulate sending funds to the bank account as instructed in the quote. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxSend tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the session to revoke. - required: true - schema: - type: string - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxSendRequest' responses: - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. + '200': + description: Funds received successfully content: application/json: schema: - $ref: '#/components/schemas/AuthSignedRequestChallenge' - '204': - description: Session revoked successfully. + $ref: '#/components/schemas/TransactionOneOf' '400': description: Bad request content: @@ -4978,13 +4856,19 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Session not found + description: Quote not found content: application/json: schema: @@ -4995,87 +4879,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/sessions/{id}/refresh: + /sandbox/uma/receive: post: - summary: Refresh an authentication session + summary: Simulate payment send to test receiving an UMA payment description: | - Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: - - 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. - - The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. - operationId: refreshAuthSession + Simulate sending payment from an sandbox uma address to a platform customer to test payment receive. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxReceive tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the active session to refresh. - required: true - schema: - type: string - example: Session:019542f5-b3e7-1d02-0000-000000000003 - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AuthSessionRefreshRequest' - examples: - refresh: - summary: Refresh an active session - value: - clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + $ref: '#/components/schemas/SandboxUmaReceiveRequest' responses: - '201': - description: New authentication session created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/AuthSession' - examples: - session: - summary: Refreshed authentication session - value: - id: Session:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - type: EMAIL_OTP - encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf - nickname: example@lightspark.com - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:35:00Z' - expiresAt: '2026-04-08T15:50:00Z' - '202': - description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + '200': + description: Payment triggered successfully content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' - examples: - challenge: - summary: Session refresh challenge - value: - payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' - requestId: Request:019542f5-b3e7-1d02-0000-000000000010 - expiresAt: '2026-04-08T15:35:00Z' + $ref: '#/components/schemas/IncomingTransaction' '400': description: Bad request content: @@ -5083,13 +4910,19 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Session not found + description: Sender or receiver not found content: application/json: schema: @@ -5100,80 +4933,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys: + /sandbox/internal-accounts/{accountId}/fund: post: - summary: Create a delegated signing key + summary: Simulate funding an internal account description: | - Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: - - 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. - - 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. - - The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. - - Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. - operationId: createDelegatedKey + Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions. + This endpoint is only for the sandbox environment and will fail for production platforms/keys. + operationId: sandboxFundInternalAccount tags: - - Embedded Wallet Auth + - Sandbox security: - BasicAuth: [] parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. - schema: - type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 - - name: Request-Id - in: header - required: false - description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. + - name: accountId + in: path + required: true + description: The ID of the internal account to fund schema: type: string - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeyCreateRequest' + $ref: '#/components/schemas/SandboxFundRequest' examples: - create: - summary: Delegate signing to a Grid-custodied key + fundUSDAccount: + summary: Fund USD internal account with $1,000 value: - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - nickname: Card payments key + amount: 100000 + fundBTCAccount: + summary: Fund BTC internal account with 0.01 BTC + value: + amount: 1000000 responses: - '201': - description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKey' - examples: - delegatedKey: - summary: Active delegated key - value: - id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - cardId: Card:019542f5-b3e7-1d02-0000-000000000010 - fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 - accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 - nickname: Card payments key - status: ACTIVE - createdAt: '2026-04-08T15:30:01Z' - updatedAt: '2026-04-08T15:30:42Z' - '202': - description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + '200': + description: Internal account funded successfully content: application/json: schema: - $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' + $ref: '#/components/schemas/InternalAccount' '400': description: Bad request content: @@ -5181,65 +4981,92 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Card, card funding source, or Embedded Wallet funding account not found + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. + $ref: '#/components/schemas/Error403' + '404': + description: Internal account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' + /uma-providers: get: - summary: List delegated signing keys - description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. - operationId: listDelegatedKeys + summary: List available Counterparty Providers + description: | + Retrieve a list of available Counterparty Providers. The response includes basic information about each provider, such as its UMA address, name, and supported currencies. + operationId: getAvailableUmaProviders tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] + - Available UMA Providers parameters: - - name: accountId + - in: query + name: countryCode + description: The alpha-2 representation of a country, as defined by the ISO 3166-1 standard. + required: false + schema: + type: string + example: US + - in: query + name: currencyCode + description: The ISO 4217 currency code to filter providers by supported currency. + required: false + schema: + type: string + example: USD + - in: query + name: hasBlockedProviders + description: Whether to include providers which are not on your allowlist in the response. By default the response will include blocked providers. + required: false + schema: + type: boolean + - name: limit in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) required: false - description: The id of the internal account whose delegated keys to list. schema: type: string - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - name: fundingSourceId + - name: sortOrder in: query + description: Order to sort results in required: false - description: The id of the card funding source whose delegated keys to list. schema: type: string - example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + enum: + - asc + - desc + default: desc + security: + - BasicAuth: [] responses: '200': - description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. - content: - application/json: - schema: - $ref: '#/components/schemas/DelegatedKeyListResponse' - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/UmaProviderListResponse' '401': description: Unauthorized content: @@ -5252,119 +5079,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /auth/delegated-keys/{id}: - get: - summary: Get a delegated signing key - description: Retrieve a delegated signing key by its system-generated id. - operationId: getDelegatedKeyById + /tokens: + post: + summary: Create a new API token + description: Create a new API token to access the Grid APIs. + operationId: createToken tags: - - Embedded Wallet Auth + - API Tokens security: - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApiTokenCreateRequest' responses: - '200': - description: Successful operation + '201': + description: API token created successfully content: application/json: schema: - $ref: '#/components/schemas/DelegatedKey' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Delegated key not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Revoke a delegated signing key - description: | - Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. - - The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. - operationId: revokeDelegatedKey - tags: - - Embedded Wallet Auth - security: - - BasicAuth: [] - parameters: - - name: id - in: path - description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). - required: true - schema: - type: string - example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 - responses: - '204': - description: Delegated key revoked. The key can no longer authorize signing. - '400': - description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized. - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Delegated key not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents: - post: - summary: Create an agent - description: | - Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. - operationId: createAgent - tags: - - Agent Management - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AgentCreateRequest' - responses: - '201': - description: Agent created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentCreateResponse' - '400': - description: Bad request + $ref: '#/components/schemas/ApiToken' + '400': + description: Bad request content: application/json: schema: @@ -5382,56 +5120,46 @@ paths: schema: $ref: '#/components/schemas/Error500' get: - summary: List agents - description: Retrieve a paginated list of agents for the authenticated platform. - operationId: listAgents + summary: List tokens + description: | + Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match + the specified filters. If no filters are provided, returns all tokens (paginated). + operationId: listTokens tags: - - Agent Management + - API Tokens security: - BasicAuth: [] parameters: - - name: customerId + - name: name in: query - description: Filter by customer ID + description: Filter by name of the token required: false schema: type: string - - name: isPaused - in: query - description: Filter by paused status - required: false - schema: - type: boolean - - name: isConnected - in: query - description: Filter by connection status (whether the device code has been redeemed) - required: false - schema: - type: boolean - name: createdAfter in: query - description: Filter agents created after this timestamp (inclusive) + description: Filter customers created after this timestamp (inclusive) required: false schema: type: string format: date-time - name: createdBefore in: query - description: Filter agents created before this timestamp (inclusive) + description: Filter customers created before this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedAfter in: query - description: Filter agents updated after this timestamp (inclusive) + description: Filter customers updated after this timestamp (inclusive) required: false schema: type: string format: date-time - name: updatedBefore in: query - description: Filter agents updated before this timestamp (inclusive) + description: Filter customers updated before this timestamp (inclusive) required: false schema: type: string @@ -5457,7 +5185,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AgentListResponse' + $ref: '#/components/schemas/TokenListResponse' '400': description: Bad request - Invalid parameters content: @@ -5476,227 +5204,192 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/approvals: + /tokens/{tokenId}: + parameters: + - name: tokenId + in: path + description: System-generated unique token identifier + required: true + schema: + type: string get: - summary: List agent transaction approval requests - description: | - Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: listAgentApprovals + summary: Get API token by ID + description: Retrieve an API token by their system-generated ID + operationId: getTokenById tags: - - Agent Management + - API Tokens security: - BasicAuth: [] - parameters: - - name: agentId - in: query - description: Filter by agent ID - required: false - schema: - type: string - - name: customerId - in: query - description: Filter by customer ID - required: false - schema: - type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ApiToken' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Token not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me: - get: - summary: Get current agent - description: | - Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. - operationId: getAgentMe + delete: + summary: Delete API token by ID + description: Delete an API token by their system-generated ID + operationId: deleteTokenById tags: - - Agent Operations + - API Tokens security: - - AgentAuth: [] + - BasicAuth: [] responses: - '200': - description: Successful operation + '204': + description: API token deleted successfully + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Token not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}: + /internal-accounts/{id}: parameters: - - name: agentId + - name: id in: path - description: System-generated unique agent identifier + description: The id of the internal account to update. required: true schema: type: string - get: - summary: Get agent by ID - description: Retrieve an agent by its system-generated ID. - operationId: getAgentById + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account + description: | + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating wallet privacy is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - patch: - summary: Update agent - description: Update an agent's name or paused state. - operationId: updateAgent - tags: - - Agent Management + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentUpdateRequest' + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true responses: '200': - description: Agent updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/Agent' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized + description: Signed retry accepted. Returns the updated internal account. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + status: ACTIVE + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request content: application/json: schema: - $ref: '#/components/schemas/Error500' - delete: - summary: Delete agent - description: Permanently delete an agent. Connected agent software will lose access immediately. - operationId: deleteAgent - tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '204': - description: Agent deleted successfully + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Internal account not found content: application/json: schema: @@ -5707,36 +5400,69 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/policy: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - patch: - summary: Update agent policy + /internal-accounts/{id}/export: + post: + summary: Export internal account wallet credentials description: | - Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. - operationId: updateAgentPolicy + Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body. + + Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session): + + 1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key. + + The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against. + operationId: exportInternalAccount tags: - - Agent Management + - Internal Accounts security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the internal account to export. + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentPolicyUpdateRequest' + $ref: '#/components/schemas/InternalAccountExportRequest' + examples: + export: + summary: Export request (both steps) + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 responses: '200': - description: Agent policy updated successfully + description: Signed retry accepted. Returns the encrypted wallet credentials. content: application/json: schema: - $ref: '#/components/schemas/Agent' + $ref: '#/components/schemas/InternalAccountExportResponse' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': description: Bad request content: @@ -5744,13 +5470,13 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent not found + description: Internal account not found content: application/json: schema: @@ -5761,161 +5487,233 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/device-codes: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string + /auth/credentials: post: - summary: Regenerate a device code + summary: Create an authentication credential description: | - Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. - operationId: regenerateAgentDeviceCode + Register an authentication credential for an Embedded Wallet customer. + + Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered. + + Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (decrypted client-side from its `encryptedSessionSigningKey`) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`. + operationId: createAuthCredential tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] - responses: - '201': - description: New device code generated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/AgentDeviceCode' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Agent not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Agent already has an active connection and cannot regenerate a device code - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf' + examples: + emailOtp: + summary: Add an email OTP credential + value: + type: EMAIL_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + smsOtp: + summary: Add an SMS OTP credential + value: + type: SMS_OTP + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oauth: + summary: Add an OAuth credential + value: + type: OAUTH + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + passkey: + summary: Add a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: iPhone Face-ID + challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx + attestation: + credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY + clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow + transports: + - internal + - hybrid + responses: + '201': + description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: application/json: schema: - $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/approve: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to approve - required: true - schema: - type: string - post: - summary: Approve an agent action - description: | - Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. - For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: approveAgentAction - tags: - - Agent Management - security: - - BasicAuth: [] - responses: - '200': - description: Action approved successfully. Returns the updated AgentAction. + $ref: '#/components/schemas/AuthMethodResponse' + examples: + emailOtp: + summary: Email OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + smsOtp: + summary: SMS OTP credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: SMS_OTP + nickname: '+14155550123' + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + oauth: + summary: OAuth credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + passkey: + summary: Passkey credential created + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + '202': + description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Additional email OTP credential challenge + value: + type: EMAIL_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + smsOtp: + summary: Additional SMS OTP credential challenge + value: + type: SMS_OTP + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + oauth: + summary: Additional OAuth credential challenge + value: + type: OAUTH + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' + passkey: + summary: Additional passkey credential challenge + value: + type: PASSKEY + payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}' + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Action cannot be approved + description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Agent or action not found + description: Internal account not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/{agentId}/actions/{actionId}/reject: - parameters: - - name: agentId - in: path - description: System-generated unique agent identifier - required: true - schema: - type: string - - name: actionId - in: path - description: Unique identifier of the agent action to reject - required: true - schema: - type: string - post: - summary: Reject an agent action - description: | - Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. - This endpoint is called by the platform's backend using platform credentials, not by the agent itself. - operationId: rejectAgentAction + get: + summary: List authentication credentials + description: |- + Retrieve all authentication credentials registered on an Embedded Wallet internal account. + + The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`. + operationId: listAuthCredentials tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] - requestBody: - required: false - content: - application/json: - schema: - $ref: '#/components/schemas/AgentActionRejectRequest' + parameters: + - name: accountId + in: query + description: Internal account id whose authentication credentials to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Action rejected successfully. Returns the updated AgentAction. + description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AuthCredentialListResponse' + examples: + multipleCredentials: + summary: Internal account with multiple authentication credentials + value: + data: + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:01Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: OAUTH + nickname: example@lightspark.com + createdAt: '2026-04-08T15:35:00Z' + updatedAt: '2026-04-08T15:35:00Z' + - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-09T10:15:00Z' + updatedAt: '2026-04-09T10:15:00Z' + empty: + summary: No credentials registered on the account + value: + data: [] '400': - description: Bad request - Action cannot be rejected + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: @@ -5926,56 +5724,74 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Agent or action not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict - Action is not pending approval or has already been processed - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error + '500': + description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/status: - parameters: - - name: code - in: path - description: The device code to check - required: true - schema: - type: string - get: - summary: Get device code status + /auth/credentials/{id}: + delete: + summary: Revoke an authentication credential description: | - Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. - operationId: getAgentDeviceCodeStatus + Revoke an authentication credential on an Embedded Wallet internal account. + + Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account: + + 1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it. + operationId: revokeAuthCredential tags: - - Agent Management + - Embedded Wallet Auth security: - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '200': - description: Successful operation + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Authentication credential revoked successfully. + '400': + description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Device code not found + description: Authentication credential not found content: application/json: schema: @@ -5986,143 +5802,212 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/device-codes/{code}/redeem: - parameters: - - name: code - in: path - description: The device code to redeem - required: true - schema: - type: string + /auth/credentials/{id}/verify: post: - summary: Redeem device code + summary: Verify an authentication credential description: | - Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. - This endpoint does not require platform authentication — the device code itself serves as proof of authorization. - operationId: redeemAgentDeviceCode + Complete the verification step for a previously created authentication credential and issue a session. + + For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` carrying the `verificationToken` bound to the client's TEK public key — sign that token with the matching TEK private key, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion. + In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and sign a real `verificationToken` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery. + + For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request. + + On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires. + operationId: verifyAuthCredential tags: - - Agent Management - security: [] + - Embedded Wallet Auth + security: + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true + schema: + type: string + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the TEK (Target Encryption Key) keypair the client generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf' + examples: + emailOtp: + summary: Verify an email OTP credential (first leg) + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + smsOtp: + summary: Verify an SMS OTP credential (first leg) + value: + type: SMS_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + emailOtpSignedRetry: + summary: Signed retry completing an email OTP verification + description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the `verificationToken` and the `requestId` from the prior `202` response. + value: + type: EMAIL_OTP + encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}' + oauth: + summary: Verify an OAuth credential + value: + type: OAUTH + oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + passkey: + summary: Verify a passkey credential + value: + type: PASSKEY + assertion: + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0 + authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA + signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Device code redeemed successfully + description: Authentication credential verified and session issued content: application/json: schema: - $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + $ref: '#/components/schemas/AuthSession' + '202': + description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSignedRequestChallenge' + examples: + emailOtp: + summary: Email OTP verification challenge (sign and retry) + value: + type: EMAIL_OTP + payloadToSign: eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request (e.g., code already redeemed or expired) + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' '404': - description: Device code not found + description: Authentication credential not found content: application/json: schema: $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions: - get: - summary: List agent transactions + /auth/credentials/{id}/challenge: + post: + summary: Re-issue an authentication credential challenge description: | - Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. - operationId: agentListTransactions + Re-issue the challenge for an existing authentication credential. + + For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow. + + `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`. + + For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: ` to receive a session. + operationId: challengeAuthCredential tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: accountIdentifier - in: query - description: Filter by account identifier (matches either sender or receiver) - required: false - schema: - type: string - - name: senderAccountIdentifier - in: query - description: Filter by sender account identifier - required: false - schema: - type: string - - name: receiverAccountIdentifier - in: query - description: Filter by receiver account identifier - required: false - schema: - type: string - - name: status - in: query - description: Filter by transaction status - required: false - schema: - $ref: '#/components/schemas/TransactionStatus' - - name: type - in: query - description: Filter by transaction type - required: false - schema: - $ref: '#/components/schemas/TransactionType' - - name: reference - in: query - description: Filter by reference - required: false + - name: id + in: path + description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`). + required: true schema: type: string - - name: startDate - in: query - description: Filter by start date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: endDate - in: query - description: Filter by end date (inclusive) in ISO 8601 format - required: false - schema: - type: string - format: date-time - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc + requestBody: + description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint. + required: false + content: + application/json: + schema: + $ref: '#/components/schemas/AuthCredentialChallengeRequest' + examples: + passkey: + summary: Re-challenge a passkey credential + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + emailOtp: + summary: Re-challenge an email-OTP credential (empty body) + value: {} + smsOtp: + summary: Re-challenge an SMS-OTP credential (empty body) + value: {} responses: '200': - description: Successful operation + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. content: application/json: schema: - $ref: '#/components/schemas/TransactionListResponse' + $ref: '#/components/schemas/AuthCredentialResponseOneOf' + examples: + emailOtp: + summary: Email OTP challenge re-issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + nickname: example@lightspark.com + otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}''' + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + passkey: + summary: Passkey reauthentication challenge issued + value: + id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: PASSKEY + credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew + nickname: iPhone Face-ID + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729 + requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: '2026-04-08T15:35:00Z' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -6133,155 +6018,239 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Authentication credential not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '429': + description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header. + headers: + Retry-After: + description: Number of seconds to wait before retrying the request. + schema: + type: integer + content: + application/json: + schema: + $ref: '#/components/schemas/Error429' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transactions/{transactionId}: - parameters: - - name: transactionId - in: path - description: Unique identifier of the transaction - required: true - schema: - type: string + /auth/sessions: get: - summary: Get agent transaction by ID - description: | - Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. - operationId: agentGetTransaction + summary: List active sessions + description: |- + Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`. + + The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline. + operationId: listAuthSessions tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] + parameters: + - name: accountId + in: query + description: Internal account id whose sessions to list. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: '200': - description: Successful operation + description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/TransactionOneOf' - '401': - description: Unauthorized + $ref: '#/components/schemas/SessionListResponse' + '400': + description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:` identifier. content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Transaction not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes: - post: - summary: Create a transfer quote + /auth/sessions/{id}: + delete: + summary: Revoke an authentication session description: | - Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. - If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. - operationId: agentCreateQuote + Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow: + + 1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`. + + Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level. + operationId: revokeAuthSession tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key + - name: id + in: path + description: The id of the session to revoke. + required: true + schema: + type: string + - name: Grid-Wallet-Signature in: header required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call. schema: type: string - example: - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/QuoteRequest' + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: - '201': - description: Transfer quote created successfully + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/AuthSignedRequestChallenge' + '204': + description: Session revoked successfully. '400': - description: Bad request - Missing or invalid parameters + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '412': - description: Counterparty doesn't support UMA version - content: - application/json: - schema: - $ref: '#/components/schemas/Error412' - '424': - description: Counterparty issue + '404': + description: Session not found content: application/json: schema: - $ref: '#/components/schemas/Error424' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}: - parameters: - - name: quoteId - in: path - description: ID of the quote to retrieve - required: true - schema: - type: string - get: - summary: Get agent quote by ID + /auth/sessions/{id}/refresh: + post: + summary: Refresh an authentication session description: | - Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. - operationId: agentGetQuote + Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow: + + 1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "04..." }` and no signature headers. Grid builds a Grid session-refresh payload, binds the supplied `clientPublicKey` into that payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession` with an `encryptedSessionSigningKey` sealed to that client public key. + + The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead. + operationId: refreshAuthSession tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] - responses: - '200': - description: Quote retrieved successfully + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the active session to refresh. + required: true + schema: + type: string + example: Session:019542f5-b3e7-1d02-0000-000000000003 + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id + in: header + required: false + description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSessionRefreshRequest' + examples: + refresh: + summary: Refresh an active session + value: + clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2 + responses: + '201': + description: New authentication session created successfully. content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/AuthSession' + examples: + session: + summary: Refreshed authentication session + value: + id: Session:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + type: EMAIL_OTP + encryptedSessionSigningKey: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf + nickname: example@lightspark.com + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:35:00Z' + expiresAt: '2026-04-08T15:50:00Z' + '202': + description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Session refresh challenge + value: + payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}' + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call. content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Quote not found + description: Session not found content: application/json: schema: @@ -6292,75 +6261,100 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/quotes/{quoteId}/execute: - parameters: - - name: quoteId - in: path - required: true - description: The unique identifier of the quote to execute - schema: - type: string - example: Quote:019542f5-b3e7-1d02-0000-000000000001 + /auth/delegated-keys: post: - summary: Execute a quote + summary: Create a delegated signing key description: | - Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. - If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - Once executed, the quote cannot be cancelled. - operationId: agentExecuteQuote + Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow: + + 1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`. + + 3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. + + The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material. + + Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed. + operationId: createDelegatedKey tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key + - name: Grid-Wallet-Signature in: header required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call. schema: type: string - example: - - name: Grid-Wallet-Signature + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + - name: Request-Id in: header required: false - description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`. schema: type: string - example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeyCreateRequest' + examples: + create: + summary: Delegate signing to a Grid-custodied key + value: + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Card payments key responses: - '200': - description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' + '201': + description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account. content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/DelegatedKey' + examples: + delegatedKey: + summary: Active delegated key + value: + id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90 + nickname: Card payments key + status: ACTIVE + createdAt: '2026-04-08T15:30:01Z' + updatedAt: '2026-04-08T15:30:42Z' + '202': + description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`. + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge' '400': - description: Bad request - Quote cannot be executed + description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Quote not found + description: Card, card funding source, or Embedded Wallet funding account not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict - Quote already executed, expired, or in invalid state + description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one. content: application/json: schema: @@ -6371,45 +6365,42 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions: get: - summary: List agent's own actions - description: | - Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. - operationId: agentListActions + summary: List delegated signing keys + description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied. + operationId: listDelegatedKeys tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: status - in: query - description: Filter by action status - required: false - schema: - $ref: '#/components/schemas/AgentActionStatus' - - name: limit + - name: accountId in: query - description: Maximum number of results to return (default 20, max 100) required: false + description: The id of the internal account whose delegated keys to list. schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - name: fundingSourceId in: query - description: Cursor for pagination (returned from previous request) required: false + description: The id of the card funding source whose delegated keys to list. schema: type: string + example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011 responses: '200': - description: Successful operation + description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller. content: application/json: schema: - $ref: '#/components/schemas/AgentActionListResponse' + $ref: '#/components/schemas/DelegatedKeyListResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6422,30 +6413,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/actions/{actionId}: - parameters: - - name: actionId - in: path - description: Unique identifier of the agent action - required: true - schema: - type: string + /auth/delegated-keys/{id}: get: - summary: Get an agent action - description: | - Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. - operationId: agentGetAction + summary: Get a delegated signing key + description: Retrieve a delegated signing key by its system-generated id. + operationId: getDelegatedKeyById tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] + parameters: + - name: id + in: path + description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`). + required: true + schema: + type: string + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/DelegatedKey' '401': description: Unauthorized content: @@ -6453,7 +6444,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Action not found + description: Delegated key not found content: application/json: schema: @@ -6464,69 +6455,42 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: - post: - summary: Create a transfer-in + delete: + summary: Revoke a delegated signing key description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn + Revoke an `ACTIVE` delegated signing key. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes. + + The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security. + operationId: revokeDelegatedKey tags: - - Agent Operations + - Embedded Wallet Auth security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. + - name: id + in: path + description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`). + required: true schema: type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 + example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021 responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' + '204': + description: Delegated key revoked. The key can no longer authorize signing. '400': - description: Bad request - Invalid parameters + description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Account not found + description: Delegated key not found content: application/json: schema: @@ -6537,50 +6501,31 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: + /agents: post: - summary: Create a transfer-out + summary: Create an agent description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut + Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation. + operationId: createAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 + - BasicAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 + $ref: '#/components/schemas/AgentCreateRequest' responses: '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + description: Agent created successfully content: application/json: schema: - $ref: '#/components/schemas/AgentAction' + $ref: '#/components/schemas/AgentCreateResponse' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -6591,47 +6536,67 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/internal-accounts: get: - summary: List agent's internal accounts - description: | - Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. - operationId: agentListInternalAccounts + summary: List agents + description: Retrieve a paginated list of agents for the authenticated platform. + operationId: listAgents tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: currency + - name: customerId in: query - description: Filter by currency code + description: Filter by customer ID required: false schema: type: string - - name: type + - name: isPaused in: query - description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. + description: Filter by paused status required: false schema: - $ref: '#/components/schemas/InternalAccountType' + type: boolean + - name: isConnected + in: query + description: Filter by connection status (whether the device code has been redeemed) + required: false + schema: + type: boolean + - name: createdAfter + in: query + description: Filter agents created after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: createdBefore + in: query + description: Filter agents created before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedAfter + in: query + description: Filter agents updated after this timestamp (inclusive) + required: false + schema: + type: string + format: date-time + - name: updatedBefore + in: query + description: Filter agents updated before this timestamp (inclusive) + required: false + schema: + type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6653,7 +6618,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InternalAccountListResponse' + $ref: '#/components/schemas/AgentListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6666,23 +6637,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts: + /agents/approvals: get: - summary: List agent external accounts + summary: List agent transaction approval requests description: | - Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentListExternalAccounts + Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: listAgentApprovals tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] parameters: - - name: currency + - name: agentId in: query - description: Filter by currency code + description: Filter by agent ID + required: false + schema: + type: string + - name: customerId + in: query + description: Filter by customer ID + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format required: false schema: type: string + format: date-time - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -6698,13 +6689,23 @@ paths: required: false schema: type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountListResponse' + $ref: '#/components/schemas/AgentActionListResponse' '400': description: Bad request - Invalid parameters content: @@ -6723,105 +6724,103 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - post: - summary: Add an external account + /agents/me: + get: + summary: Get current agent description: | - Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. - operationId: agentCreateExternalAccount + Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials. + operationId: getAgentMe tags: - Agent Operations security: - AgentAuth: [] - requestBody: + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Agent' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccountCreateRequest' - examples: - usBankAccount: - summary: Create external US bank account - value: - currency: USD - accountInfo: - accountType: USD_ACCOUNT - accountNumber: '12345678901' - routingNumber: '123456789' - bankAccountType: CHECKING - bankName: Chase Bank - beneficiary: - beneficiaryType: INDIVIDUAL - fullName: John Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - city: San Francisco - state: CA - postalCode: '94105' - country: US - sparkWallet: - summary: Create external Spark wallet - value: - currency: BTC - accountInfo: - accountType: SPARK_WALLET - address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu + schema: + type: string + get: + summary: Get agent by ID + description: Retrieve an agent by its system-generated ID. + operationId: getAgentById + tags: + - Agent Management + security: + - BasicAuth: [] responses: - '201': - description: External account created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Bad request + '200': + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Agent' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - External account already exists + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/external-accounts/{externalAccountId}: - parameters: - - name: externalAccountId - in: path - description: System-generated unique external account identifier - required: true - schema: - type: string - get: - summary: Get agent external account by ID - description: | - Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentGetExternalAccount + patch: + summary: Update agent + description: Update an agent's name or paused state. + operationId: updateAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentUpdateRequest' responses: '200': - description: Successful operation + description: Agent updated successfully content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/Agent' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -6829,7 +6828,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Agent not found content: application/json: schema: @@ -6841,17 +6840,16 @@ paths: schema: $ref: '#/components/schemas/Error500' delete: - summary: Delete agent external account - description: | - Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. - operationId: agentDeleteExternalAccount + summary: Delete agent + description: Permanently delete an agent. Connected agent software will lose access immediately. + operationId: deleteAgent tags: - - Agent Operations + - Agent Management security: - - AgentAuth: [] + - BasicAuth: [] responses: '204': - description: External account deleted successfully + description: Agent deleted successfully '401': description: Unauthorized content: @@ -6859,7 +6857,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Agent not found content: application/json: schema: @@ -6870,18 +6868,21 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /cards: - post: - summary: Issue a card + /agents/{agentId}/policy: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + patch: + summary: Update agent policy description: | - Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - - If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. - - New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). - operationId: createCard + Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately. + operationId: updateAgentPolicy tags: - - Cards + - Agent Management security: - BasicAuth: [] requestBody: @@ -6889,25 +6890,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CardCreateRequest' - examples: - virtualCard: - summary: Issue a virtual card with one funding source - value: - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + $ref: '#/components/schemas/AgentPolicyUpdateRequest' responses: - '201': - description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. + '200': + description: Agent policy updated successfully content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/Agent' '400': - description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. + description: Bad request content: application/json: schema: @@ -6918,86 +6910,44 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error501' - get: - summary: List cards + $ref: '#/components/schemas/Error500' + /agents/{agentId}/device-codes: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + post: + summary: Regenerate a device code description: | - Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. - operationId: listCards + Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated. + operationId: regenerateAgentDeviceCode tags: - - Cards + - Agent Management security: - BasicAuth: [] - parameters: - - name: cardholderId - in: query - description: Filter by cardholder (customer) id. - required: false - schema: - type: string - - name: accountId - in: query - description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. - required: false - schema: - type: string - - name: platformCardId - in: query - description: Filter by platform-specific card identifier. - required: false - schema: - type: string - - name: state - in: query - description: Filter by card state. - required: false - schema: - $ref: '#/components/schemas/CardState' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string - - name: sortOrder - in: query - description: Order to sort results in - required: false - schema: - type: string - enum: - - asc - - desc - default: desc responses: - '200': - description: Successful operation + '201': + description: New device code generated successfully content: application/json: schema: - $ref: '#/components/schemas/CardListResponse' + $ref: '#/components/schemas/AgentDeviceCode' '400': - description: Bad request - Invalid parameters + description: Bad request content: application/json: schema: @@ -7008,41 +6958,62 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '500': - description: Internal service error + '404': + description: Agent not found content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error404' + '409': + description: Conflict - Agent already has an active connection and cannot regenerate a device code content: application/json: schema: - $ref: '#/components/schemas/Error501' - /cards/{id}: - parameters: - - name: id - in: path - description: System-generated unique card identifier + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/{agentId}/actions/{actionId}/approve: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier required: true schema: type: string - get: - summary: Get a card - description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. - operationId: getCardById + - name: actionId + in: path + description: Unique identifier of the agent action to approve + required: true + schema: + type: string + post: + summary: Approve an agent action + description: | + Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`. + For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: approveAgentAction tags: - - Cards + - Agent Management security: - BasicAuth: [] responses: '200': - description: Successful operation + description: Action approved successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/Card' + $ref: '#/components/schemas/AgentAction' + '400': + description: Bad request - Action cannot be approved + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': description: Unauthorized content: @@ -7050,128 +7021,80 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Card not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' - '500': - description: Internal service error + '409': + description: Conflict - Action is not pending approval or has already been processed content: application/json: schema: - $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error501' - patch: - summary: Update a card + $ref: '#/components/schemas/Error500' + /agents/{agentId}/actions/{actionId}/reject: + parameters: + - name: agentId + in: path + description: System-generated unique agent identifier + required: true + schema: + type: string + - name: actionId + in: path + description: Unique identifier of the agent action to reject + required: true + schema: + type: string + post: + summary: Reject an agent action description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - - - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - - Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): - - 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. - - 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. - - Effects: - - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - - `state: ACTIVE`: normal authorization behavior resumes. - - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. - - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. - operationId: updateCardById + Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed. + This endpoint is called by the platform's backend using platform credentials, not by the agent itself. + operationId: rejectAgentAction tags: - - Cards + - Agent Management security: - BasicAuth: [] - parameters: - - name: Grid-Wallet-Signature - in: header - required: false - description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. - schema: - type: string - example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= - - name: Request-Id - in: header - required: false - description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. - schema: - type: string - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: - required: true + required: false content: application/json: schema: - $ref: '#/components/schemas/CardUpdateRequest' - examples: - freeze: - summary: Freeze an active card - value: - state: FROZEN - unfreeze: - summary: Unfreeze a frozen card - value: - state: ACTIVE - updateFundingSources: - summary: Replace the card's bound funding sources - value: - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - freezeAndUpdateSources: - summary: Freeze the card and replace its funding sources in one call - value: - state: FROZEN - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - close: - summary: Permanently close the card - value: - state: CLOSED + $ref: '#/components/schemas/AgentActionRejectRequest' responses: '200': - description: Signed retry accepted. Returns the updated card. - content: - application/json: - schema: - $ref: '#/components/schemas/Card' - '202': - description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + description: Action rejected successfully. Returns the updated AgentAction. content: application/json: schema: - $ref: '#/components/schemas/SignedRequestChallenge' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. + description: Bad request - Action cannot be rejected content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' '404': - description: Card not found + description: Agent or action not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: Conflict - Action is not pending approval or has already been processed content: application/json: schema: @@ -7182,54 +7105,38 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. - content: - application/json: - schema: - $ref: '#/components/schemas/Error501' - /cards/{id}/reveal: + /agents/device-codes/{code}/status: parameters: - - name: id + - name: code in: path - description: System-generated unique card identifier + description: The device code to check required: true schema: type: string - post: - summary: Reveal card details - description: |- - Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. - - Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. - - Every reveal is audit-logged with the requesting actor. - operationId: revealCard + get: + summary: Get device code status + description: | + Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent. + operationId: getAgentDeviceCodeStatus tags: - - Cards + - Agent Management security: - BasicAuth: [] responses: '200': - description: Reveal URL minted. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/CardRevealResponse' + $ref: '#/components/schemas/AgentDeviceCodeStatusResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Card not found + description: Device code not found content: application/json: schema: @@ -7240,70 +7147,141 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - '501': - description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + /agents/device-codes/{code}/redeem: + parameters: + - name: code + in: path + description: The device code to redeem + required: true + schema: + type: string + post: + summary: Redeem device code + description: | + Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely. + This endpoint does not require platform authentication — the device code itself serves as proof of authorization. + operationId: redeemAgentDeviceCode + tags: + - Agent Management + security: [] + responses: + '200': + description: Device code redeemed successfully content: application/json: schema: - $ref: '#/components/schemas/Error501' - /sandbox/cards/{id}/simulate/authorization: - post: - summary: Simulate a card authorization + $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse' + '400': + description: Bad request (e.g., code already redeemed or expired) + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Device code not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/transactions: + get: + summary: List agent transactions description: | - Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - - The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - - | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - - Production returns `404` on this path. - operationId: sandboxSimulateCardAuthorization - tags: - - Sandbox + Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted. + operationId: agentListTransactions + tags: + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - required: true - description: The id of the card to simulate an authorization against. + - name: accountIdentifier + in: query + description: Filter by account identifier (matches either sender or receiver) + required: false schema: type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardAuthorizationRequest' - examples: - coffeeAuth: - summary: Approved $12.50 auth at a coffee shop - value: - amount: 1250 - currency: - code: USD - merchant: - descriptor: BLUE BOTTLE COFFEE SF - mcc: '5814' - country: US - declinedInsufficientFunds: - summary: Declined — insufficient funds (descriptor suffix `002`) - value: - amount: 50000 - currency: - code: USD - merchant: - descriptor: AMAZON RETAIL US-002 - mcc: '5942' - country: US + - name: senderAccountIdentifier + in: query + description: Filter by sender account identifier + required: false + schema: + type: string + - name: receiverAccountIdentifier + in: query + description: Filter by receiver account identifier + required: false + schema: + type: string + - name: status + in: query + description: Filter by transaction status + required: false + schema: + $ref: '#/components/schemas/TransactionStatus' + - name: type + in: query + description: Filter by transaction type + required: false + schema: + $ref: '#/components/schemas/TransactionType' + - name: reference + in: query + description: Filter by reference + required: false + schema: + type: string + - name: startDate + in: query + description: Filter by start date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: endDate + in: query + description: Filter by end date (inclusive) in ISO 8601 format + required: false + schema: + type: string + format: date-time + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc responses: '200': - description: Simulated authorization processed. Returns the resulting card transaction. + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/CardTransaction' + $ref: '#/components/schemas/TransactionListResponse' '400': description: Bad request - Invalid parameters content: @@ -7316,92 +7294,44 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Card not found (also returned in production for this path) - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/clearing: - post: - summary: Simulate a card clearing + /agents/me/transactions/{transactionId}: + parameters: + - name: transactionId + in: path + description: Unique identifier of the transaction + required: true + schema: + type: string + get: + summary: Get agent transaction by ID description: | - Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. - - - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). - - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. - - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). - - Production returns `404` on this path. - operationId: sandboxSimulateCardClearing + Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer. + operationId: agentGetTransaction tags: - - Sandbox + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: id - in: path - required: true - description: The id of the card the clearing applies to. - schema: - type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SandboxCardClearingRequest' - examples: - tipOnTopClearing: - summary: Clearing larger than auth — exercises post-hoc pull - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 - authorizationExpiry: - summary: Clearing of 0 — exercises authorization expiry - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 0 + - AgentAuth: [] responses: '200': - description: Simulated clearing processed. Returns the updated card transaction. - content: - application/json: - schema: - $ref: '#/components/schemas/CardTransaction' - '400': - description: Bad request - Invalid parameters + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/TransactionOneOf' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - request was made with a production platform token - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' '404': - description: Card or card transaction not found + description: Transaction not found content: application/json: schema: @@ -7412,47 +7342,41 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /sandbox/cards/{id}/simulate/return: + /agents/me/quotes: post: - summary: Simulate a card return + summary: Create a transfer quote description: | - Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). - - Production returns `404` on this path. - operationId: sandboxSimulateCardReturn + Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy. + If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move. + operationId: agentCreateQuote tags: - - Sandbox + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: id - in: path - required: true - description: The id of the card the return applies to. + - name: Idempotency-Key + in: header + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - example: Card:019542f5-b3e7-1d02-0000-000000000010 + example: requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/SandboxCardReturnRequest' - examples: - fullRefund: - summary: Full refund of a $15.00 settled transaction - value: - cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 - amount: 1500 + $ref: '#/components/schemas/QuoteRequest' responses: - '200': - description: Simulated return processed. Returns the updated card transaction. + '201': + description: Transfer quote created successfully content: application/json: schema: - $ref: '#/components/schemas/CardTransaction' + $ref: '#/components/schemas/Quote' '400': - description: Bad request - Invalid parameters + description: Bad request - Missing or invalid parameters content: application/json: schema: @@ -7464,74 +7388,140 @@ paths: schema: $ref: '#/components/schemas/Error401' '403': - description: Forbidden - request was made with a production platform token + description: Forbidden - Agent policy does not permit this operation content: application/json: schema: $ref: '#/components/schemas/Error403' - '404': - description: Card or card transaction not found + '412': + description: Counterparty doesn't support UMA version content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error412' + '424': + description: Counterparty issue + content: + application/json: + schema: + $ref: '#/components/schemas/Error424' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins: - post: - summary: Register an existing provider-created stablecoin - description: | - Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. - operationId: registerStablecoin - tags: - - Stablecoins + /agents/me/quotes/{quoteId}: + parameters: + - name: quoteId + in: path + description: ID of the quote to retrieve + required: true + schema: + type: string + get: + summary: Get agent quote by ID + description: | + Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent. + operationId: agentGetQuote + tags: + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + responses: + '200': + description: Quote retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Quote' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Quote not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /agents/me/quotes/{quoteId}/execute: + parameters: + - name: quoteId + in: path + required: true + description: The unique identifier of the quote to execute + schema: + type: string + example: Quote:019542f5-b3e7-1d02-0000-000000000001 + post: + summary: Execute a quote + description: | + Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy. + If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + Once executed, the quote cannot be cancelled. + operationId: agentExecuteQuote + tags: + - Agent Operations + security: + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinRegisterRequest' + example: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9 responses: - '201': - description: Stablecoin registered + '200': + description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.' content: application/json: schema: - $ref: '#/components/schemas/Stablecoin' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Quote cannot be executed content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized + description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`. content: application/json: schema: $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' '404': - description: Referenced stablecoin provider account not found + description: Quote not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Conflict + description: Conflict - Quote already executed, expired, or in invalid state content: application/json: schema: @@ -7542,30 +7532,23 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/actions: get: - summary: List stablecoins - description: Retrieve stablecoins registered to the authenticated platform. - operationId: listStablecoins + summary: List agent's own actions + description: | + Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval. + operationId: agentListActions tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: issuanceStatus - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinIssuanceStatus' - - name: gridOperationsStatus - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinGridOperationsStatus' - - name: provider + - name: status in: query + description: Filter by action status required: false schema: - $ref: '#/components/schemas/StablecoinProvider' + $ref: '#/components/schemas/AgentActionStatus' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7577,7 +7560,7 @@ paths: default: 20 - name: cursor in: query - description: Opaque cursor returned as `nextCursor` from the previous response. + description: Cursor for pagination (returned from previous request) required: false schema: type: string @@ -7587,13 +7570,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StablecoinListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/AgentActionListResponse' '401': description: Unauthorized content: @@ -7606,29 +7583,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins/{stablecoinId}: + /agents/me/actions/{actionId}: parameters: - - name: stablecoinId + - name: actionId in: path - description: System-generated stablecoin identifier + description: Unique identifier of the agent action required: true schema: type: string get: - summary: Get a stablecoin - description: Retrieve a single registered stablecoin by its Grid identifier. - operationId: getStablecoin + summary: Get an agent action + description: | + Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed. + operationId: agentGetAction tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Stablecoin' + $ref: '#/components/schemas/AgentAction' '401': description: Unauthorized content: @@ -7636,7 +7614,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Stablecoin not found + description: Action not found content: application/json: schema: @@ -7647,46 +7625,51 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoins/{stablecoinId}/mints: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/transfer-in: post: - summary: Create a stablecoin mint operation + summary: Create a transfer-in description: | - Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. - operationId: createStablecoinMint + Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. + operationId: agentCreateTransferIn tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this mint request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinMintRequest' + $ref: '#/components/schemas/TransferInRequest' + examples: + transferIn: + summary: Transfer from external to internal account + value: + source: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + amount: 12550 responses: '201': - description: Mint operation created + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -7697,70 +7680,68 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin or referenced account not found + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict + $ref: '#/components/schemas/Error403' + '404': + description: Account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '503': - description: Provider temporarily unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/Error503' - /stablecoins/{stablecoinId}/burns: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/transfer-out: post: - summary: Create a stablecoin burn operation + summary: Create a transfer-out description: | - Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. - operationId: createStablecoinBurn + Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. + If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. + operationId: agentCreateTransferOut tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - name: Idempotency-Key in: header - description: Required idempotency key for retrying this burn request safely. - required: true + required: false + description: | + A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. schema: type: string - maxLength: 255 + example: 550e8400-e29b-41d4-a716-446655440000 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinBurnRequest' - responses: - '201': - description: Burn operation created - content: - application/json: + $ref: '#/components/schemas/TransferOutRequest' + examples: + transferOut: + summary: Transfer from internal to external account + value: + source: + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + amount: 12550 + responses: + '201': + description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. + content: + application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' + $ref: '#/components/schemas/AgentAction' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: @@ -7771,53 +7752,47 @@ paths: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin or referenced account not found + '403': + description: Forbidden - Agent policy does not permit this operation or spending limit exceeded content: application/json: schema: - $ref: '#/components/schemas/Error404' - '409': - description: Conflict + $ref: '#/components/schemas/Error403' + '404': + description: Account not found content: application/json: schema: - $ref: '#/components/schemas/Error409' + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - '503': - description: Provider temporarily unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/Error503' - /stablecoins/{stablecoinId}/operations: - parameters: - - name: stablecoinId - in: path - description: System-generated stablecoin identifier - required: true - schema: - type: string + /agents/me/internal-accounts: get: - summary: List stablecoin issuer operations - description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. - operationId: listStablecoinOperations + summary: List agent's internal accounts + description: | + Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy. + operationId: agentListInternalAccounts tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] parameters: - - name: operationType + - name: currency in: query - description: Filter operations by type. When omitted, both mints and burns are returned. + description: Filter by currency code required: false schema: - $ref: '#/components/schemas/StablecoinOperationType' + type: string + - name: type + in: query + description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, or `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts. + required: false + schema: + $ref: '#/components/schemas/InternalAccountType' - name: limit in: query description: Maximum number of results to return (default 20, max 100) @@ -7829,7 +7804,7 @@ paths: default: 20 - name: cursor in: query - description: Opaque cursor returned as `nextCursor` from the previous response. + description: Cursor for pagination (returned from previous request) required: false schema: type: string @@ -7839,97 +7814,127 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperationListResponse' + $ref: '#/components/schemas/InternalAccountListResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-operations/{stablecoinOperationId}: - parameters: - - name: stablecoinOperationId - in: path - description: System-generated stablecoin issuer operation identifier - required: true - schema: - type: string + /agents/me/external-accounts: get: - summary: Get a stablecoin issuer operation - description: Retrieve a single stablecoin issuer operation by its Grid identifier. - operationId: getStablecoinOperation + summary: List agent external accounts + description: | + Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentListExternalAccounts tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] + parameters: + - name: currency + in: query + description: Filter by currency code + required: false + schema: + type: string + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/StablecoinOperation' - '401': - description: Unauthorized + $ref: '#/components/schemas/ExternalAccountListResponse' + '400': + description: Bad request - Invalid parameters content: application/json: schema: - $ref: '#/components/schemas/Error401' - '404': - description: Stablecoin operation not found + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/Error404' + $ref: '#/components/schemas/Error401' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts: post: - summary: Link a stablecoin provider account + summary: Add an external account description: | - Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. - operationId: linkStablecoinProviderAccount + Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted. + operationId: agentCreateExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: Idempotency-Key - in: header - description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. - required: true - schema: - type: string - maxLength: 255 + - AgentAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' + $ref: '#/components/schemas/ExternalAccountCreateRequest' + examples: + usBankAccount: + summary: Create external US bank account + value: + currency: USD + accountInfo: + accountType: USD_ACCOUNT + accountNumber: '12345678901' + routingNumber: '123456789' + bankAccountType: CHECKING + bankName: Chase Bank + beneficiary: + beneficiaryType: INDIVIDUAL + fullName: John Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + city: San Francisco + state: CA + postalCode: '94105' + country: US + sparkWallet: + summary: Create external Spark wallet + value: + currency: BTC + accountInfo: + accountType: SPARK_WALLET + address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu responses: '201': - description: Stablecoin provider account linked + description: External account created successfully content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccount' + $ref: '#/components/schemas/ExternalAccount' '400': description: Bad request content: @@ -7943,7 +7948,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict + description: Conflict - External account already exists content: application/json: schema: @@ -7954,88 +7959,60 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /agents/me/external-accounts/{externalAccountId}: + parameters: + - name: externalAccountId + in: path + description: System-generated unique external account identifier + required: true + schema: + type: string get: - summary: List stablecoin provider account links - description: Retrieve stablecoin provider account links for the authenticated platform. - operationId: listStablecoinProviderAccounts + summary: Get agent external account by ID + description: | + Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentGetExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] - parameters: - - name: provider - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProvider' - - name: status - in: query - required: false - schema: - $ref: '#/components/schemas/StablecoinProviderAccountStatus' - - name: limit - in: query - description: Maximum number of results to return (default 20, max 100) - required: false - schema: - type: integer - minimum: 1 - maximum: 100 - default: 20 - - name: cursor - in: query - description: Cursor for pagination (returned from previous request) - required: false - schema: - type: string + - AgentAuth: [] responses: '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/StablecoinProviderAccountListResponse' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/ExternalAccount' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '500': description: Internal service error content: application/json: schema: $ref: '#/components/schemas/Error500' - /stablecoin-provider-accounts/{stablecoinProviderAccountId}: - parameters: - - name: stablecoinProviderAccountId - in: path - description: System-generated stablecoin provider account link identifier - required: true - schema: - type: string - get: - summary: Get a stablecoin provider account link - description: Retrieve a stablecoin provider account link by id for the authenticated platform. - operationId: getStablecoinProviderAccount + delete: + summary: Delete agent external account + description: | + Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. + operationId: agentDeleteExternalAccount tags: - - Stablecoins + - Agent Operations security: - - BasicAuth: [] + - AgentAuth: [] responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/StablecoinProviderAccount' + '204': + description: External account deleted successfully '401': description: Unauthorized content: @@ -8043,7 +8020,7 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: Stablecoin provider account link not found + description: External account not found content: application/json: schema: @@ -8054,1189 +8031,3167 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' -webhooks: - agent-action: + /cards: post: - summary: Agent action pending approval webhook + summary: Issue a card description: | - Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Issue a new card for a cardholder. Every card must be bound to at least one funding source at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + If any funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions. - The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. - operationId: agentActionWebhook + New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails). + operationId: createCard tags: - - Webhooks + - Cards security: - - WebhookSignature: [] + - BasicAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/AgentActionWebhook' + $ref: '#/components/schemas/CardCreateRequest' examples: - pendingApproval: - summary: Agent action pending approval + virtualCard: + summary: Issue a virtual card with one funding source value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: AGENT_ACTION.PENDING_APPROVAL - timestamp: '2025-10-03T15:00:00Z' - data: - id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 - agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 - platformCustomerId: user-a1b2c3 - status: PENDING_APPROVAL - type: EXECUTE_QUOTE - quote: - id: Quote:019542f5-b3e7-1d02-0000-000000000006 - status: PENDING - expiresAt: '2025-10-03T15:00:30Z' - createdAt: '2025-10-03T15:00:00Z' - source: - sourceType: ACCOUNT - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - sendingCurrency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivingCurrency: - code: INR - name: Indian Rupee - symbol: ₹ - decimals: 2 - totalSendingAmount: 50000 - totalReceivingAmount: 4625000 - exchangeRate: 92.5 - feesIncluded: 250 - transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 - createdAt: '2025-10-03T15:00:00Z' - updatedAt: '2025-10-03T15:00:00Z' + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 responses: - '200': - description: Webhook received and acknowledged. + '201': + description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it. + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + '400': + description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, with `FUNDING_SOURCE_INELIGIBLE` when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - incoming-payment: - post: - summary: Incoming payment webhook and approval mechanism + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + get: + summary: List cards description: | - Webhook that is called when an incoming payment is received by a customer's UMA address. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - - ### Payment Approval Flow - When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: - - 1. The client should check the `counterpartyInformation` against their requirements - 2. To APPROVE the payment synchronously, return a 200 OK response - 3. To REJECT the payment, return a 403 Forbidden response with an Error object - 4. To request more information, return a 422 Unprocessable Entity with specific missing fields - 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. - - The Grid system will proceed or cancel the payment based on your response. - - For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. - operationId: incomingPaymentWebhook + Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, state, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller. + operationId: listCards tags: - - Webhooks + - Cards security: - - WebhookSignature: [] - requestBody: + - BasicAuth: [] + parameters: + - name: cardholderId + in: query + description: Filter by cardholder (customer) id. + required: false + schema: + type: string + - name: accountId + in: query + description: Filter by internal account id. Returns cards whose `fundingSources` array contains the given internal account id. + required: false + schema: + type: string + - name: platformCardId + in: query + description: Filter by platform-specific card identifier. + required: false + schema: + type: string + - name: state + in: query + description: Filter by card state. + required: false + schema: + $ref: '#/components/schemas/CardState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + - name: sortOrder + in: query + description: Order to sort results in + required: false + schema: + type: string + enum: + - asc + - desc + default: desc + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CardListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}: + parameters: + - name: id + in: path + description: System-generated unique card identifier required: true - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhook' - examples: - pendingPayment: - summary: Pending payment example requiring approval - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.PENDING - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: PENDING - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - counterpartyInformation: - FULL_NAME: John Sender - BIRTH_DATE: '1985-06-15' - NATIONALITY: US - reconciliationInstructions: - reference: REF-123456789 - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: POSTAL_ADDRESS - mandatory: false - incomingCompletedPayment: - summary: Completed payment notification - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: INCOMING - direction: CREDIT - destination: - destinationType: UMA_ADDRESS - umaAddress: $recipient@uma.domain - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@external.domain - receiverUmaAddress: $recipient@uma.domain - receivedAmount: - amount: 50000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: Payment for services - reconciliationInstructions: - reference: REF-123456789 - incomingCompletedCryptoPayment: - summary: Completed payment funded from an external crypto wallet - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: INCOMING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - type: INCOMING - direction: CREDIT - source: - sourceType: REALTIME_FUNDING - currency: USDC - onChainTransaction: - transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf - network: SOLANA - destination: - destinationType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC deposit from self-custody wallet + schema: + type: string + get: + summary: Get a card + description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL. + operationId: getCardById + tags: + - Cards + security: + - BasicAuth: [] responses: '200': - description: | - Webhook received successfully. - For PENDING transactions, this indicates approval to proceed with the payment. - If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. - content: - application/json: - schema: - $ref: '#/components/schemas/IncomingPaymentWebhookResponse' - '202': - description: | - Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in - cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. - The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. - '400': - description: Bad request + description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/Card' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '403': - description: | - Forbidden - Payment rejected by the client. - Only applicable for PENDING transactions. + '404': + description: Card not found content: application/json: schema: - $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error content: application/json: schema: - $ref: '#/components/schemas/Error409' - '422': - description: | - Unprocessable Entity - Additional counterparty information required. - Only applicable for PENDING transactions. + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. content: application/json: schema: - $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' - outgoing-payment: - post: - summary: Outgoing payment status webhook + $ref: '#/components/schemas/Error501' + patch: + summary: Update a card description: | - Webhook that is called when an outgoing payment's status changes. - This endpoint should be implemented by clients of the Grid API. + Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. + - `fundingSources`, when supplied, fully replaces the card's bound funding sources. Array order determines the priority Authorization Decisioning tries them in. Each id must belong to the cardholder and be denominated in the card's currency; the list must contain at least one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + Because both updates are sensitive state changes, this endpoint uses Grid's 202 → signed-retry pattern (same shape as `DELETE /auth/credentials/{id}` and `POST /internal-accounts/{id}/export`): - This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. - operationId: outgoingPaymentWebhook + 1. Call `PATCH /cards/{id}` with the target fields and no signing headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Sign the `payloadToSign` with the session private key of a verified authentication credential on the card's owning internal account and retry with the signature as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `200` with the updated `Card`. + + Effects: + - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. + - `state: ACTIVE`: normal authorization behavior resumes. + - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. + - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + + The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + operationId: updateCardById tags: - - Webhooks + - Cards security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Signature over the `payloadToSign` returned in a prior `202` response, produced with the session private key of a verified authentication credential on the card's owning internal account and base64-encoded. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: MEUCIQDx7k2N0aK4p8f3vR9J6yT5wL1mB0sXnG2hQ4vJ8zYkCgIgZ4rP9dT7eWfU3oM6KjR1qSpNvBwL0tXyA2iG8fH5dE= + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/OutgoingPaymentWebhook' + $ref: '#/components/schemas/CardUpdateRequest' examples: - outgoingCompletedPayment: - summary: Completed outgoing payment + freeze: + summary: Freeze an active card value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - receivedAmount: - amount: 9706 - currency: - code: EUR - name: Euro - symbol: € - decimals: 2 - exchangeRate: 0.92 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' - paymentInstructions: [] - rateDetails: - counterpartyMultiplier: 1.08 - counterpartyFixedFee: 10 - gridApiMultiplier: 0.925 - gridApiFixedFee: 10 - gridApiVariableFeeRate: 0.003 - gridApiVariableFeeAmount: 30 - outgoingCompletedCryptoPayment: - summary: Completed crypto payout to an external wallet + state: FROZEN + unfreeze: + summary: Unfreeze a frozen card value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: OUTGOING_PAYMENT.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000009 - status: COMPLETED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 - onChainTransaction: - transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx - network: SOLANA - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - sentAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - receivedAmount: - amount: 100000 - currency: - code: USDC - name: USD Coin - symbol: '' - decimals: 6 - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 - settledAt: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: USDC withdrawal to self-custody wallet - paymentInstructions: [] - failedPayment: - summary: Failed outgoing payment + state: ACTIVE + updateFundingSources: + summary: Replace the card's bound funding sources value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Transaction:019542f5-b3e7-1d02-0000-000000000005 - status: FAILED - type: OUTGOING - direction: DEBIT - source: - sourceType: ACCOUNT - accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - destinationType: ACCOUNT - accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 18d3e5f7b4a9c2 - senderUmaAddress: $sender@uma.domain - receiverUmaAddress: $recipient@external.domain - sentAmount: - amount: 10550 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - createdAt: '2025-08-15T14:25:18Z' - quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - failureReason: QUOTE_EXECUTION_FAILED + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + freezeAndUpdateSources: + summary: Freeze the card and replace its funding sources in one call + value: + state: FROZEN + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + close: + summary: Permanently close the card + value: + state: CLOSED responses: '200': - description: Webhook received successfully + description: Signed retry accepted. Returns the updated card. + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + '202': + description: Challenge issued. The response contains a `payloadToSign` that must be signed with the session private key of a verified authentication credential on the card's owning internal account, along with a `requestId` that must be echoed back on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' '400': - description: Bad request + description: Bad request. Returned with `FUNDING_SOURCE_INELIGIBLE` when a supplied funding source does not belong to the cardholder or is not denominated in the card's currency, and for general invalid parameters. content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending update challenge for this card, or when the `Request-Id` does not match an unexpired pending challenge. content: application/json: schema: $ref: '#/components/schemas/Error401' + '404': + description: Card not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' '409': - description: Conflict - Webhook has already been processed (duplicate id) + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `state` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE | FROZEN → CLOSED` (e.g. trying to un-freeze a `CLOSED` card); with `CARD_ALREADY_CLOSED` when `state: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: $ref: '#/components/schemas/Error409' - test-webhook: + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /cards/{id}/reveal: + parameters: + - name: id + in: path + description: System-generated unique card identifier + required: true + schema: + type: string post: - summary: Test webhook for integration verification - description: | - Webhook that is sent once to verify your webhook endpoint is correctly set up. - This is sent when you configure or update your platform settings with a webhook URL. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + summary: Reveal card details + description: |- + Mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. This is the only way to obtain a reveal URL — the `Card` resource never carries one. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers. - This webhook is purely for testing your endpoint integration and signature verification. - operationId: testWebhook + Every reveal is audit-logged with the requesting actor. + operationId: revealCard tags: - - Webhooks + - Cards security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestWebhookRequest' - examples: - testWebhook: - summary: Test webhook example - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000001 - type: TEST - timestamp: '2025-08-15T14:32:00Z' - data: {} + - BasicAuth: [] responses: '200': - description: Webhook received successfully. This confirms your webhook endpoint is properly configured. - '400': - description: Bad request + description: Reveal URL minted. content: application/json: schema: - $ref: '#/components/schemas/Error400' + $ref: '#/components/schemas/CardRevealResponse' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session). content: application/json: schema: - $ref: '#/components/schemas/Error409' - bulk-upload: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '501': + description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error501' + /sandbox/cards/{id}/simulate/authorization: post: - summary: Bulk upload status webhook + summary: Simulate a card authorization description: | - Webhook that is called when a bulk customer upload job completes or fails. - This endpoint should be implemented by clients of the Grid API. + Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + The decisioning outcome is controlled by the last three characters of `merchant.descriptor`: - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + | Suffix | Outcome | | ------ | ------- | | `002` | Decline — `INSUFFICIENT_FUNDS` (the pull on the funding source fails) | | `003` | Decline — `CARD_PAUSED` (intended to verify a frozen card refuses auths) | | `005` | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006` | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved | - This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. - operationId: bulkUploadWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate an authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/BulkUploadWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - completedUpload: - summary: Successful bulk upload completion + coffeeAuth: + summary: Approved $12.50 auth at a coffee shop value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.COMPLETED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: COMPLETED - progress: - total: 5000 - processed: 5000 - successful: 5000 - failed: 0 - errors: [] - failedUpload: - summary: Failed bulk upload + amount: 1250 + currency: + code: USD + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + declinedInsufficientFunds: + summary: Declined — insufficient funds (descriptor suffix `002`) value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD.FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Job:019542f5-b3e7-1d02-0000-000000000006 - status: FAILED - progress: - total: 5000 - processed: 5000 - successful: 0 - failed: 5000 - errors: - - correlationId: row_1 - error: - code: invalid_csv_format - message: Invalid CSV format - details: - reason: missing_required_column - column: umaAddress + amount: 50000 + currency: + code: USD + merchant: + descriptor: AMAZON RETAIL US-002 + mcc: '5942' + country: US responses: - '200': - description: Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - invitation-claimed: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/clearing: post: - summary: Invitation claimed webhook + summary: Simulate a card clearing description: | - Webhook that is called when an invitation is claimed by a customer. - This endpoint should be implemented by platform clients of the Grid API. - - When a customer claims an invitation, this webhook is triggered to notify the platform that: - 1. The invitation has been successfully claimed - 2. The invitee UMA address is now associated with the invitation - 3. The invitation status has changed from PENDING to CLAIMED - - This allows platforms to: - - Track invitation usage and conversion rates - - Trigger onboarding flows for new customers who joined via invitation - - Apply referral bonuses or rewards to the inviter - - Update their UI to reflect the claimed status + Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth). + - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted. + - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases). - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: invitationClaimedWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardClearing tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the clearing applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InvitationClaimedWebhook' + $ref: '#/components/schemas/SandboxCardClearingRequest' examples: - claimedInvitation: - summary: Invitation claimed notification + tipOnTopClearing: + summary: Clearing larger than auth — exercises post-hoc pull value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION.CLAIMED - timestamp: '2025-09-01T15:45:00Z' - data: - code: 019542f5 - createdAt: '2025-09-01T14:30:00Z' - claimedAt: '2025-09-01T15:45:00Z' - inviterUma: $inviter@uma.domain - inviteeUma: $invitee@uma.domain - status: CLAIMED - url: https://uma.me/i/019542f5 + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + authorizationExpiry: + summary: Clearing of 0 — exercises authorization expiry + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 0 responses: - '200': - description: Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - customer-update: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/return: post: - summary: Customer status change + summary: Simulate a card return description: | - Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. - This endpoint should be implemented by clients of the Grid API. + Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` on the parent and either flips the parent to `REFUNDED` (full refund) or keeps it `SETTLED` with a non-zero `refundedAmount` (partial refund). - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Production returns `404` on this path. + operationId: sandboxSimulateCardReturn + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the return applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardReturnRequest' + examples: + fullRefund: + summary: Full refund of a $15.00 settled transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 1500 + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/balance_inquiry: + post: + summary: Simulate a card balance inquiry + description: | + Simulate a balance-inquiry authorization against a card in the sandbox environment. A balance inquiry is always a `0`-amount authorization, so the request carries no `amount` — only the `merchant`. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: customerStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardBalanceInquiry tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a balance inquiry against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CustomerWebhook' + $ref: '#/components/schemas/SandboxCardBalanceInquiryRequest' examples: - kycApprovedWebhook: - summary: When an individual customer KYC has been approved + balanceInquiry: + summary: Balance inquiry at a fuel pump value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: CUSTOMER.KYC_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 9f84e0c2a72c4fa - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $john.doe@uma.domain.com - kycStatus: APPROVED - fullName: John Michael Doe - birthDate: '1990-01-15' - nationality: US - address: - line1: 123 Main Street - line2: Apt 4B - city: San Francisco - state: CA - postalCode: '94105' - country: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-07-21T17:32:28Z' - isDeleted: false - kycRejectedWebhook: - summary: When an individual customer KYC has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: CUSTOMER.KYC_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000002 - platformCustomerId: 4b7c1e9d3f5a8e2 - customerType: INDIVIDUAL - region: US - currencies: - - USD - - USDC - umaAddress: $jane.smith@uma.domain.com - kycStatus: REJECTED - fullName: Jane Smith - birthDate: '1988-03-22' - nationality: US - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybApprovedWebhook: - summary: When a business customer KYB has been approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000009 - type: CUSTOMER.KYB_APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000003 - platformCustomerId: 7a2f9d4e1b8c3f5 - customerType: BUSINESS - region: US - currencies: - - USD - - USDC - umaAddress: $acme.corp@uma.domain.com - kybStatus: APPROVED - address: - line1: 456 Business Ave - city: New York - state: NY - postalCode: '10001' - country: US - businessInfo: - legalName: Acme Corporation - registrationNumber: '12345678' - taxId: 98-7654321 - incorporatedOn: '2018-03-14' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false - kybRejectedWebhook: - summary: When a business customer KYB has been rejected - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000010 - type: CUSTOMER.KYB_REJECTED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Customer:019542f5-b3e7-1d02-0000-000000000004 - platformCustomerId: 3c8e5f2a9d1b7e4 - customerType: BUSINESS - region: US - currencies: - - USD - umaAddress: $globex.inc@uma.domain.com - kybStatus: REJECTED - address: - line1: 789 Corporate Blvd - city: Chicago - state: IL - postalCode: '60601' - country: US - businessInfo: - legalName: Globex Inc - taxId: 47-1234567 - incorporatedOn: '2015-09-01' - createdAt: '2025-07-21T17:32:28Z' - updatedAt: '2025-08-15T14:32:00Z' - isDeleted: false + merchant: + descriptor: SHELL OIL 12345678 + mcc: '5541' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - internal-account-status: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/credit_authorization: post: - summary: Internal account status webhook + summary: Simulate a card credit authorization description: | - Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + Simulate an inbound credit authorization (a merchant-initiated credit to the card, e.g. a refund pushed as an authorization) in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - ### Event types - - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. - - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. - operationId: internalAccountStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardCreditAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a credit authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/InternalAccountStatusWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - balanceDecrease: - summary: A transaction just cleared a customer account and the balance has decreased - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INTERNAL_ACCOUNT.BALANCE_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: ACTIVE - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 12500 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' - statusUpdated: - summary: The status of an internal account changed (e.g., frozen by Grid) + creditAuth: + summary: $25.00 credit authorization value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INTERNAL_ACCOUNT.STATUS_UPDATED - timestamp: '2025-08-15T14:32:00Z' - data: - id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: INTERNAL_FIAT - status: FROZEN - balance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - totalBalance: - amount: 10000 - currency: - code: USD - name: United States Dollar - symbol: $ - decimals: 2 - fundingPaymentInstructions: [] - createdAt: '2025-08-01T10:00:00Z' - updatedAt: '2025-08-15T14:32:00Z' + amount: 2500 + currency: + code: USD + merchant: + descriptor: ACME MARKETPLACE + mcc: '5942' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - verification-update: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/financial_authorization: post: - summary: Verification status change + summary: Simulate a card financial authorization description: | - Webhook that is called when a customer's KYC/KYB verification status changes. - This endpoint should be implemented by clients of the Grid API. + Simulate a single-message financial authorization (an authorization that clears in the same message, e.g. an ATM withdrawal or other dual-message-exempt flow) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - ### Authentication - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid API public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: verificationStatusWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardFinancialAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a financial authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/VerificationWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - approved: - summary: Verification approved - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: VERIFICATION.APPROVED - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000010 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: APPROVED - errors: [] - createdAt: '2025-08-15T14:00:00Z' - resolveErrors: - summary: Verification requires action + financialAuth: + summary: $80.00 single-message ATM withdrawal value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000031 - type: VERIFICATION.RESOLVE_ERRORS - timestamp: '2025-08-15T14:32:00Z' - data: - id: Verification:019542f5-b3e7-1d02-0000-000000000011 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - verificationStatus: RESOLVE_ERRORS - errors: - - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 - type: MISSING_PROOF_OF_ADDRESS_DOCUMENT - acceptedDocumentTypes: - - PROOF_OF_ADDRESS - reason: Proof of address document is required - createdAt: '2025-08-15T14:00:00Z' + amount: 8000 + currency: + code: USD + merchant: + descriptor: ATM WITHDRAWAL 24TH ST + mcc: '6011' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - card-state-change: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/financial_credit_authorization: post: - summary: Card state change + summary: Simulate a card financial credit authorization description: | - Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication + Simulate a single-message financial credit authorization (a credit to the card that clears in the same message, e.g. an ATM deposit or push credit) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardStateChangeWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardFinancialCreditAuthorization tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card to simulate a financial credit authorization against. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardStateChangeWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' examples: - activated: - summary: Card transitioned from PROCESSING to ACTIVE - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000020 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:11:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCardId: card-emp-aary-001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - processorRef: card_b81c2a4f - issuerRef: lead_card_7a1b9c3d - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:11:00Z' - issuerRejected: - summary: Card rejected by issuer during provisioning - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000021 - type: CARD.STATE_CHANGE - timestamp: '2026-05-08T14:12:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000011 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: CLOSED - stateReason: ISSUER_REJECTED - form: VIRTUAL - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:12:00Z' - frozen: - summary: Card frozen by the platform + financialCreditAuth: + summary: $40.00 single-message push credit value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000022 - type: CARD.STATE_CHANGE - timestamp: '2026-05-09T09:00:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: FROZEN - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-09T09:00:00Z' + amount: 4000 + currency: + code: USD + merchant: + descriptor: ACME PAYOUTS + mcc: '6012' + country: US responses: - '200': - description: | - Webhook received successfully + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' '400': - description: Bad request + description: Bad request - Invalid parameters content: application/json: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized - Signature validation failed + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) + '403': + description: Forbidden - request was made with a production platform token content: application/json: schema: - $ref: '#/components/schemas/Error409' - card-funding-source-change: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/authorization_advice: post: - summary: Card funding source change + summary: Simulate a card authorization advice description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. + Simulate an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization on a `CardTransaction` in the sandbox environment. The `amount` is the new *total* authorized amount the advice re-sizes to. The parent transaction must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook + Production returns `404` on this path. + operationId: sandboxSimulateCardAuthorizationAdvice tags: - - Webhooks + - Sandbox security: - - WebhookSignature: [] + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the advice applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' + $ref: '#/components/schemas/SandboxCardAuthorizationAdviceRequest' examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} + resizeUp: + summary: Re-size an open auth up to a new total of $20.00 value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: 2000 responses: - '200': - description: | + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/credit_authorization_advice: + post: + summary: Simulate a card credit authorization advice + description: | + Simulate a network-initiated `CREDIT_AUTHORIZATION_ADVICE` against a card in the sandbox environment. Like `simulate/credit_authorization`, it is keyed by the card (PAN) and `merchant` rather than an existing transaction. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook. + + As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table. + + Production returns `404` on this path. + operationId: sandboxSimulateCardCreditAuthorizationAdvice + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the advice applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardAuthorizationRequest' + examples: + creditAdvice: + summary: $25.00 credit authorization advice + value: + amount: 2500 + currency: + code: USD + merchant: + descriptor: ACME MARKETPLACE + mcc: '5942' + country: US + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card not found (also returned in production for this path) + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /sandbox/cards/{id}/simulate/return_reversal: + post: + summary: Simulate a card return reversal + description: | + Simulate a `RETURN_REVERSAL` against an existing `REFUNDED` card transaction in the sandbox environment — reversing a previously settled return. The parent transaction must be in `REFUNDED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook. + + Note: authorization reversal / void has no dedicated card-issuer simulate path (the void simulator emits a distinct `VOID` event), so — like authorization expiry — it is not exposed here. + + Production returns `404` on this path. + operationId: sandboxSimulateCardReturnReversal + tags: + - Sandbox + security: + - BasicAuth: [] + parameters: + - name: id + in: path + required: true + description: The id of the card the return reversal applies to. + schema: + type: string + example: Card:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardTransactionRefRequest' + examples: + returnReversal: + summary: Reverse a settled return on a refunded transaction + value: + cardTransactionId: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + responses: + '202': + description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event. + content: + application/json: + schema: + $ref: '#/components/schemas/SandboxCardSimulationResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: Forbidden - request was made with a production platform token + content: + application/json: + schema: + $ref: '#/components/schemas/Error403' + '404': + description: Card or card transaction not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins: + post: + summary: Register an existing provider-created stablecoin + description: | + Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment. + operationId: registerStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinRegisterRequest' + responses: + '201': + description: Stablecoin registered + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Referenced stablecoin provider account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoins + description: Retrieve stablecoins registered to the authenticated platform. + operationId: listStablecoins + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: issuanceStatus + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinIssuanceStatus' + - name: gridOperationsStatus + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinGridOperationsStatus' + - name: provider + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProvider' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins/{stablecoinId}: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin + description: Retrieve a single registered stablecoin by its Grid identifier. + operationId: getStablecoin + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Stablecoin' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoins/{stablecoinId}/mints: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin mint operation + description: | + Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinMint + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this mint request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinMintRequest' + responses: + '201': + description: Mint operation created + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/burns: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + post: + summary: Create a stablecoin burn operation + description: | + Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received. + operationId: createStablecoinBurn + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Required idempotency key for retrying this burn request safely. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinBurnRequest' + responses: + '201': + description: Burn operation created + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin or referenced account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '503': + description: Provider temporarily unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/Error503' + /stablecoins/{stablecoinId}/operations: + parameters: + - name: stablecoinId + in: path + description: System-generated stablecoin identifier + required: true + schema: + type: string + get: + summary: List stablecoin issuer operations + description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination. + operationId: listStablecoinOperations + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: operationType + in: query + description: Filter operations by type. When omitted, both mints and burns are returned. + required: false + schema: + $ref: '#/components/schemas/StablecoinOperationType' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Opaque cursor returned as `nextCursor` from the previous response. + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperationListResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-operations/{stablecoinOperationId}: + parameters: + - name: stablecoinOperationId + in: path + description: System-generated stablecoin issuer operation identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin issuer operation + description: Retrieve a single stablecoin issuer operation by its Grid identifier. + operationId: getStablecoinOperation + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinOperation' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin operation not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-provider-accounts: + post: + summary: Link a stablecoin provider account + description: | + Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode. + operationId: linkStablecoinProviderAccount + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: Idempotency-Key + in: header + description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected. + required: true + schema: + type: string + maxLength: 255 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest' + responses: + '201': + description: Stablecoin provider account linked + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccount' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + get: + summary: List stablecoin provider account links + description: Retrieve stablecoin provider account links for the authenticated platform. + operationId: listStablecoinProviderAccounts + tags: + - Stablecoins + security: + - BasicAuth: [] + parameters: + - name: provider + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProvider' + - name: status + in: query + required: false + schema: + $ref: '#/components/schemas/StablecoinProviderAccountStatus' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccountListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /stablecoin-provider-accounts/{stablecoinProviderAccountId}: + parameters: + - name: stablecoinProviderAccountId + in: path + description: System-generated stablecoin provider account link identifier + required: true + schema: + type: string + get: + summary: Get a stablecoin provider account link + description: Retrieve a stablecoin provider account link by id for the authenticated platform. + operationId: getStablecoinProviderAccount + tags: + - Stablecoins + security: + - BasicAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/StablecoinProviderAccount' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Stablecoin provider account link not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' +webhooks: + agent-action: + post: + summary: Agent action pending approval webhook + description: | + Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`. + operationId: agentActionWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AgentActionWebhook' + examples: + pendingApproval: + summary: Agent action pending approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: AGENT_ACTION.PENDING_APPROVAL + timestamp: '2025-10-03T15:00:00Z' + data: + id: AgentAction:019542f5-b3e7-1d02-0000-000000000099 + agentId: Agent:019542f5-b3e7-1d02-0000-000000000042 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000010 + platformCustomerId: user-a1b2c3 + status: PENDING_APPROVAL + type: EXECUTE_QUOTE + quote: + id: Quote:019542f5-b3e7-1d02-0000-000000000006 + status: PENDING + expiresAt: '2025-10-03T15:00:30Z' + createdAt: '2025-10-03T15:00:00Z' + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + sendingCurrency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivingCurrency: + code: INR + name: Indian Rupee + symbol: ₹ + decimals: 2 + totalSendingAmount: 50000 + totalReceivingAmount: 4625000 + exchangeRate: 92.5 + feesIncluded: 250 + platformFeesIncluded: 0 + transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099 + createdAt: '2025-10-03T15:00:00Z' + updatedAt: '2025-10-03T15:00:00Z' + responses: + '200': + description: Webhook received and acknowledged. + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + incoming-payment: + post: + summary: Incoming payment webhook and approval mechanism + description: | + Webhook that is called when an incoming payment is received by a customer's UMA address. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Payment Approval Flow + When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism: + + 1. The client should check the `counterpartyInformation` against their requirements + 2. To APPROVE the payment synchronously, return a 200 OK response + 3. To REJECT the payment, return a 403 Forbidden response with an Error object + 4. To request more information, return a 422 Unprocessable Entity with specific missing fields + 5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible. + + The Grid system will proceed or cancel the payment based on your response. + + For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational. + operationId: incomingPaymentWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhook' + examples: + pendingPayment: + summary: Pending payment example requiring approval + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: PENDING + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + counterpartyInformation: + FULL_NAME: John Sender + BIRTH_DATE: '1985-06-15' + NATIONALITY: US + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: POSTAL_ADDRESS + mandatory: false + incomingCompletedPayment: + summary: Completed payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + incomingCompletedCryptoPayment: + summary: Completed payment funded from an external crypto wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + type: INCOMING + direction: CREDIT + source: + sourceType: REALTIME_FUNDING + currency: USDC + onChainTransaction: + transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf + network: SOLANA + destination: + destinationType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC deposit from self-custody wallet + incomingRefundedPayment: + summary: Refunded incoming payment notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000011 + type: INCOMING_PAYMENT.REFUND_COMPLETED + timestamp: '2025-08-15T14:40:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: INCOMING + direction: CREDIT + destination: + destinationType: UMA_ADDRESS + umaAddress: $recipient@uma.domain + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@external.domain + receiverUmaAddress: $recipient@uma.domain + receivedAmount: + amount: 50000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + description: Payment for services + reconciliationInstructions: + reference: REF-123456789 + failureReason: OFFRAMP_FAILED + refund: + reference: REF-123456789-REFUND + initiatedAt: '2025-08-15T14:35:00Z' + settledAt: '2025-08-15T14:40:00Z' + status: COMPLETED + reason: TRANSACTION_FAILED + responses: + '200': + description: | + Webhook received successfully. + For PENDING transactions, this indicates approval to proceed with the payment. + If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookResponse' + '202': + description: | + Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in + cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment. + The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '403': + description: | + Forbidden - Payment rejected by the client. + Only applicable for PENDING transactions. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '422': + description: | + Unprocessable Entity - Additional counterparty information required. + Only applicable for PENDING transactions. + content: + application/json: + schema: + $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse' + outgoing-payment: + post: + summary: Outgoing payment status webhook + description: | + Webhook that is called when an outgoing payment's status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. + operationId: outgoingPaymentWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OutgoingPaymentWebhook' + examples: + outgoingCompletedPayment: + summary: Completed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + receivedAmount: + amount: 9706 + currency: + code: EUR + name: Euro + symbol: € + decimals: 2 + exchangeRate: 0.92 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: + counterpartyMultiplier: 1.08 + counterpartyFixedFee: 10 + gridApiMultiplier: 0.925 + gridApiFixedFee: 10 + gridApiVariableFeeRate: 0.003 + gridApiVariableFeeAmount: 30 + outgoingCompletedCryptoPayment: + summary: Completed crypto payout to an external wallet + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000009 + status: COMPLETED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789 + onChainTransaction: + transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx + network: SOLANA + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + sentAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + receivedAmount: + amount: 100000 + currency: + code: USDC + name: USD Coin + symbol: '' + decimals: 6 + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010 + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: USDC withdrawal to self-custody wallet + paymentInstructions: [] + failedPayment: + summary: Failed outgoing payment + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Transaction:019542f5-b3e7-1d02-0000-000000000005 + status: FAILED + type: OUTGOING + direction: DEBIT + source: + sourceType: ACCOUNT + accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + senderUmaAddress: $sender@uma.domain + receiverUmaAddress: $recipient@external.domain + sentAmount: + amount: 10550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + createdAt: '2025-08-15T14:25:18Z' + quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 + failureReason: QUOTE_EXECUTION_FAILED + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + test-webhook: + post: + summary: Test webhook for integration verification + description: | + Webhook that is sent once to verify your webhook endpoint is correctly set up. + This is sent when you configure or update your platform settings with a webhook URL. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is purely for testing your endpoint integration and signature verification. + operationId: testWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestWebhookRequest' + examples: + testWebhook: + summary: Test webhook example + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 + type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} + responses: + '200': + description: Webhook received successfully. This confirms your webhook endpoint is properly configured. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + bulk-upload: + post: + summary: Bulk upload status webhook + description: | + Webhook that is called when a bulk customer upload job completes or fails. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results. + operationId: bulkUploadWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BulkUploadWebhook' + examples: + completedUpload: + summary: Successful bulk upload completion + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: COMPLETED + progress: + total: 5000 + processed: 5000 + successful: 5000 + failed: 0 + errors: [] + failedUpload: + summary: Failed bulk upload + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Job:019542f5-b3e7-1d02-0000-000000000006 + status: FAILED + progress: + total: 5000 + processed: 5000 + successful: 0 + failed: 5000 + errors: + - correlationId: row_1 + error: + code: invalid_csv_format + message: Invalid CSV format + details: + reason: missing_required_column + column: umaAddress + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + invitation-claimed: + post: + summary: Invitation claimed webhook + description: | + Webhook that is called when an invitation is claimed by a customer. + This endpoint should be implemented by platform clients of the Grid API. + + When a customer claims an invitation, this webhook is triggered to notify the platform that: + 1. The invitation has been successfully claimed + 2. The invitee UMA address is now associated with the invitation + 3. The invitation status has changed from PENDING to CLAIMED + + This allows platforms to: + - Track invitation usage and conversion rates + - Trigger onboarding flows for new customers who joined via invitation + - Apply referral bonuses or rewards to the inviter + - Update their UI to reflect the claimed status + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: invitationClaimedWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InvitationClaimedWebhook' + examples: + claimedInvitation: + summary: Invitation claimed notification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: + code: 019542f5 + createdAt: '2025-09-01T14:30:00Z' + claimedAt: '2025-09-01T15:45:00Z' + inviterUma: $inviter@uma.domain + inviteeUma: $invitee@uma.domain + status: CLAIMED + url: https://uma.me/i/019542f5 + responses: + '200': + description: Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + customer-update: + post: + summary: Customer status change + description: | + Webhook that is called when the status of a customer is updated, including KYC and KYB status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: customerStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CustomerWebhook' + examples: + kycApprovedWebhook: + summary: When an individual customer KYC has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: Seattle + state: WA + postalCode: '98101' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When an individual customer KYC has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + region: US + currencies: + - USD + - USDC + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybApprovedWebhook: + summary: When a business customer KYB has been approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000009 + type: CUSTOMER.KYB_APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000003 + platformCustomerId: 7a2f9d4e1b8c3f5 + customerType: BUSINESS + region: US + currencies: + - USD + - USDC + umaAddress: $acme.corp@uma.domain.com + kybStatus: APPROVED + address: + line1: 456 Business Ave + city: Seattle + state: WA + postalCode: '98101' + country: US + businessInfo: + legalName: Acme Corporation + registrationNumber: '12345678' + taxId: 98-7654321 + incorporatedOn: '2018-03-14' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + kybRejectedWebhook: + summary: When a business customer KYB has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000010 + type: CUSTOMER.KYB_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000004 + platformCustomerId: 3c8e5f2a9d1b7e4 + customerType: BUSINESS + region: US + currencies: + - USD + umaAddress: $globex.inc@uma.domain.com + kybStatus: REJECTED + address: + line1: 789 Corporate Blvd + city: Chicago + state: IL + postalCode: '60601' + country: US + businessInfo: + legalName: Globex Inc + taxId: 47-1234567 + incorporatedOn: '2015-09-01' + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + internal-account-status: + post: + summary: Internal account status webhook + description: | + Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + + ### Event types + - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object. + - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object. + operationId: internalAccountStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccountStatusWebhook' + examples: + balanceDecrease: + summary: A transaction just cleared a customer account and the balance has decreased + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INTERNAL_ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: ACTIVE + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 12500 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + statusUpdated: + summary: The status of an internal account changed (e.g., frozen by Grid) + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INTERNAL_ACCOUNT.STATUS_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: INTERNAL_FIAT + status: FROZEN + balance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + totalBalance: + amount: 10000 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + verification-update: + post: + summary: Verification status change + description: | + Webhook that is called when a customer's KYC/KYB verification status changes. + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid API public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: verificationStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationWebhook' + examples: + approved: + summary: Verification approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: VERIFICATION.APPROVED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: APPROVED + errors: [] + createdAt: '2025-08-15T14:00:00Z' + resolveErrors: + summary: Verification requires action + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000031 + type: VERIFICATION.RESOLVE_ERRORS + timestamp: '2025-08-15T14:32:00Z' + data: + id: Verification:019542f5-b3e7-1d02-0000-000000000011 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + verificationStatus: RESOLVE_ERRORS + errors: + - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: MISSING_PROOF_OF_ADDRESS_DOCUMENT + acceptedDocumentTypes: + - PROOF_OF_ADDRESS + reason: Proof of address document is required + createdAt: '2025-08-15T14:00:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-state-change: + post: + summary: Card state change + description: | + Webhook that is called when a card's lifecycle state changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardStateChangeWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardStateChangeWebhook' + examples: + activated: + summary: Card transitioned from PROCESSING to ACTIVE + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-aary-001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + processorRef: card_b81c2a4f + issuerRef: lead_card_7a1b9c3d + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:11:00Z' + issuerRejected: + summary: Card rejected by issuer during provisioning + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000021 + type: CARD.STATE_CHANGE + timestamp: '2026-05-08T14:12:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000011 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: CLOSED + stateReason: ISSUER_REJECTED + form: VIRTUAL + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:12:00Z' + frozen: + summary: Card frozen by the platform + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000022 + type: CARD.STATE_CHANGE + timestamp: '2026-05-09T09:00:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: FROZEN + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-09T09:00:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-funding-source-change: + post: + summary: Card funding source change + description: | + Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardFundingSourceChangeWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardFundingSourceChangeWebhook' + examples: + fundingSourcesReplaced: + summary: Funding sources replaced via PATCH /cards/{id} + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000030 + type: CARD.FUNDING_SOURCE_CHANGE + timestamp: '2026-05-08T14:30:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 + state: ACTIVE + stateReason: null + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSources: + - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + currency: USD + createdAt: '2026-05-08T14:10:00Z' + updatedAt: '2026-05-08T14:30:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + card-transaction: + post: + summary: Card transaction + description: | + Webhook that is called on every state transition of a card `CardTransaction`. Fires when an authorization is approved (`CARD_TRANSACTION.AUTHORIZED`), as clearings settle against it (`CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`), when settled funds are returned (`CARD_TRANSACTION.REFUNDED`), and when a pull or confirmation fails (`CARD_TRANSACTION.EXCEPTION`). The payload carries the full `CardTransaction` resource. + + This endpoint should be implemented by clients of the Grid API. + + ### Authentication + + The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + operationId: cardTransactionWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CardTransactionWebhook' + examples: + authorized: + summary: Authorization approved + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: CARD_TRANSACTION.AUTHORIZED + timestamp: '2026-05-09T10:00:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: AUTHORIZED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 1 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 0 + totalAmount: 0 + authorizedAt: '2026-05-09T10:00:00Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-09T10:00:00Z' + settled: + summary: All clearings posted + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: CARD_TRANSACTION.SETTLED + timestamp: '2026-05-09T15:42:11Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: SETTLED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 0 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 1 + totalAmount: 12550 + authorizedAt: '2026-05-09T10:00:00Z' + lastEventAt: '2026-05-09T15:42:11Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-09T15:42:11Z' + refunded: + summary: Settled funds returned by the merchant + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000042 + type: CARD_TRANSACTION.REFUNDED + timestamp: '2026-05-10T09:15:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_b81c2a4f + status: REFUNDED + direction: DEBIT + merchant: + descriptor: BLUE BOTTLE COFFEE SF + mcc: '5814' + country: US + authorizedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + refundedAmount: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 12550 + pendingCount: 0 + refundSummary: + count: 1 + totalAmount: 12550 + settlementSummary: + count: 1 + totalAmount: 12550 + authorizedAt: '2026-05-09T10:00:00Z' + lastEventAt: '2026-05-10T09:15:00Z' + createdAt: '2026-05-09T10:00:00Z' + updatedAt: '2026-05-10T09:15:00Z' + exception: + summary: Settled to the network but the funding-source pull failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000043 + type: CARD_TRANSACTION.EXCEPTION + timestamp: '2026-05-09T16:05:00Z' + data: + type: CARD + id: CardTransaction:019542f5-b3e7-1d02-0000-000000000101 + cardId: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 18d3e5f7b4a9c2 + issuerTransactionToken: lithic_txn_c92d3b5a + status: EXCEPTION + direction: DEBIT + merchant: + descriptor: UBER EATS + mcc: '5812' + country: US + authorizedAmount: + amount: 4200 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + settledAmount: + amount: 4200 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + pullSummary: + count: 1 + totalAmount: 0 + pendingCount: 0 + refundSummary: + count: 0 + totalAmount: 0 + settlementSummary: + count: 1 + totalAmount: 4200 + authorizedAt: '2026-05-09T15:30:00Z' + lastEventAt: '2026-05-09T16:05:00Z' + createdAt: '2026-05-09T15:30:00Z' + updatedAt: '2026-05-09T16:05:00Z' + responses: + '200': + description: | Webhook received successfully '400': description: Bad request @@ -9439,10 +11394,149 @@ components: example: support@acme.com logoUrl: type: string - format: uri - maxLength: 512 - description: URL to a PNG logo for the OTP email. Resized to 340x124px. - example: https://acme.com/logo.png + format: uri + maxLength: 512 + description: URL to a PNG logo for the OTP email. Resized to 340x124px. + example: https://acme.com/logo.png + CardTokenization2FAEmailConfig: + type: object + description: Email branding and sender settings for card-tokenization authentication messages. Invalid or unverified sender identities can cause delivery to fail. + properties: + fromAddress: + type: string + format: email + maxLength: 255 + description: Sender address for card-tokenization authentication emails. + example: cards@acme.com + fromName: + type: string + maxLength: 255 + description: Sender display name. + example: Acme Cards + replyToAddress: + type: string + format: email + maxLength: 255 + description: Reply-to address for card-tokenization authentication emails. + example: support@acme.com + subject: + type: string + maxLength: 255 + description: Subject for the authentication email. + example: Your Acme card verification code + bodyText: + type: string + maxLength: 2000 + description: | + Plain-text message content. Lightspark inserts the authentication code into + a controlled text and HTML template; arbitrary HTML and template variables + are not supported. + example: Use this code to finish adding your Acme card to your digital wallet. + CardTokenization2FASmsConfig: + type: object + description: SMS settings for card-tokenization authentication messages delivered through a Lightspark-managed Twilio sender. + properties: + templateSid: + type: string + maxLength: 64 + description: | + Twilio Verify template SID to use for this platform. An invalid or unavailable + template can cause delivery to fail. + example: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bodyText: + type: string + maxLength: 480 + description: | + Plain-text fallback message used when Twilio Verify is unavailable for the + authentication code. Lightspark appends the code to this text. + example: Use this code to finish adding your Acme card to your digital wallet. + CardTokenization2FAConfig: + type: object + description: Per-platform branding for authentication codes sent when a cardholder adds a Grid-issued card to a digital wallet. Updates apply to subsequent delivery attempts. Invalid sender or provider configuration can cause delivery to fail. + properties: + displayName: + type: string + maxLength: 255 + description: Platform name displayed in authentication messages. + example: Acme + logoUrl: + type: string + format: uri + pattern: ^https:// + maxLength: 512 + description: HTTPS URL of the logo displayed in email messages. + example: https://acme.com/card-email-logo.png + email: + $ref: '#/components/schemas/CardTokenization2FAEmailConfig' + sms: + $ref: '#/components/schemas/CardTokenization2FASmsConfig' + FeeType: + type: string + enum: + - CROSS_CURRENCY_TRANSACTION + description: | + The kind of activity this fee applies to. + + - `CROSS_CURRENCY_TRANSACTION` — fee charged on a cross-currency Grid transaction + (source currency differs from destination currency). + example: CROSS_CURRENCY_TRANSACTION + Currency: + type: object + properties: + code: + type: string + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: + type: string + description: Full name of the currency + example: United States Dollar + symbol: + type: string + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + CurrencyAmount: + type: object + required: + - amount + - currency + properties: + amount: + type: integer + format: int64 + description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) + example: 12550 + currency: + $ref: '#/components/schemas/Currency' + FeeConfig: + type: object + description: A platform-configured fee collected by Grid and settled to the platform internal account. There can be at most one fee config for a given fee type and source currency pair. The fee will apply to all transactions of the fee type that originate in the source currency. + properties: + feeType: + $ref: '#/components/schemas/FeeType' + sourceCurrency: + type: string + description: Currency code of the sending side this fee applies to. Only `USD` is accepted today; other currencies return a `NOT_IMPLEMENTED` error. + example: USD + variableFeeBps: + type: integer + minimum: 0 + maximum: 10000 + description: Variable fee in basis points (1 bps = 0.01%) to apply to a transaction's source-currency amount. + example: 30 + fixedFee: + $ref: '#/components/schemas/CurrencyAmount' + description: Fixed fee charged per transaction, in the smallest unit of the source currency. The fixed fee currency must match the fee config's `sourceCurrency`. + required: + - feeType + - sourceCurrency + - variableFeeBps + - fixedFee PlatformConfig: type: object properties: @@ -9484,6 +11578,19 @@ components: Embedded-wallet branding and OTP settings for this platform. Present only when the platform has configured embedded-wallet support; omitted otherwise. + cardTokenization2faConfig: + $ref: '#/components/schemas/CardTokenization2FAConfig' + description: | + Branding and sender configuration for card-tokenization authentication + messages. This configuration is independent of embedded-wallet support. + feeConfigs: + type: array + items: + $ref: '#/components/schemas/FeeConfig' + description: | + Platform-collected fees that should be added on top of + Grid-collected fees. Contains every currently-active fee config + for the platform. createdAt: type: string format: date-time @@ -9584,6 +11691,66 @@ components: Fields omitted from the nested object are left unchanged. Omit this field at the top level to leave the embedded-wallet configuration unchanged entirely. + cardTokenization2faConfig: + $ref: '#/components/schemas/CardTokenization2FAConfig' + description: | + Update card-tokenization authentication branding and delivery settings. + Fields omitted from the nested object are left unchanged. Changes apply + to subsequent delivery attempts. + feeConfigs: + type: array + items: + $ref: '#/components/schemas/FeeConfig' + description: | + Merge-by-key upsert of platform fee configs, keyed by + `(feeType, sourceCurrency)`. Setting variable and fixed fees to 0 for + an existing fee config deactivates it. Only `sourceCurrency: USD` is + accepted today. Omit this field to leave fee configs unchanged. + FieldConstraint: + type: object + description: Machine-readable validator hint accompanying a 400 `INVALID_INPUT` error. Consumers use it to drive form UI (input types, dropdowns, masking, length limits) and to pre-validate the field client-side before re-submitting. Fields are additive. + properties: + format: + type: string + description: Named format the value must satisfy — HTML5 input type names (`email`, `tel`, `url`, `date`, ...) or semantic slugs (`iso3166-1-alpha-2`, `bcp47-language-tag`, `us-ssn`, `e.164`). + example: email + pattern: + type: string + description: Regular expression the value must match (JavaScript-flavor). + example: ^\d{5}(-\d{4})?$ + enum: + type: array + items: + type: string + description: Allowed values when the field is drawn from a fixed set. + example: + - SSN + - ITIN + - NON_US_TAX_ID + minLength: + type: integer + description: Minimum length in characters. + example: 1 + maxLength: + type: integer + description: Maximum length in characters. + example: 500 + FieldError: + type: object + required: + - field + description: One field-level validation failure. Field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`) emit an array of these under `details.errors` so platforms can render form-field-level UX for every failure in a single round-trip. + properties: + field: + type: string + description: Dot-notation path to the offending field. + example: taxIdentifier + constraint: + $ref: '#/components/schemas/FieldConstraint' + message: + type: string + description: Human-readable explanation of what's wrong with this field. + example: Value is not one of the allowed enum members. Error400: type: object required: @@ -9685,7 +11852,13 @@ components: description: Error message details: type: object - description: Additional error details + description: Additional error details. Shape varies by `code`. For field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`), `details.errors[]` enumerates every invalid field so platforms can render form-field-level UX for the entire request in a single round-trip. + properties: + errors: + type: array + description: One entry per invalid field. Present on field-validation errors from submit endpoints. + items: + $ref: '#/components/schemas/FieldError' additionalProperties: true Error501: type: object @@ -9716,26 +11889,6 @@ components: type: object description: Additional error details additionalProperties: true - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 PaymentRail: type: string enum: @@ -10011,32 +12164,195 @@ components: type: string description: Country code (ISO 3166-1 alpha-2) example: US - IndividualCustomerFields: + IdentificationType: + type: string + enum: + - SSN + - ITIN + - EIN + - NON_US_TAX_ID + description: Type of tax identification + example: SSN + IndividualSourceOfFundsCategory: + type: string + enum: + - SALARY + - SELF_EMPLOYMENT_INCOME + - INVESTMENT_INCOME + - PENSION + - RENTAL_INCOME + - GIFT + - INHERITANCE + - LOAN + - SAVINGS + - SALE_OF_ASSETS + - OTHER + description: A structured source-of-funds category for an individual customer (FLOW of funds into this account). Distinct from `SourceOfFundsCategory`, which covers business customers. + example: SALARY + SourceOfWealthCategory: + type: string + enum: + - SALARY + - BUSINESS_INCOME + - INVESTMENTS + - INHERITANCE + - PROPERTY_SALE + - GIFT + - RETIREMENT + - SAVINGS + - OTHER + description: Origin of the customer's accumulated wealth (STOCK). Distinct from `SourceOfFundsCategory` which describes the FLOW of funds for this specific account. + example: SALARY + PurposeOfAccount: + type: string + enum: + - CONTRACTOR_PAYOUTS + - CREATOR_PAYOUTS + - EMPLOYEE_PAYOUTS + - MARKETPLACE_SELLER_PAYOUTS + - SUPPLIER_PAYMENTS + - CROSS_BORDER_B2B + - AR_AUTOMATION + - AP_AUTOMATION + - EMBEDDED_PAYMENTS + - PLATFORM_FEE_COLLECTION + - P2P_TRANSFERS + - CHARITABLE_DONATIONS + - OTHER + description: The intended purpose for using the Grid account + example: CONTRACTOR_PAYOUTS + ExpectedMonthlyTransactionCount: + type: string + enum: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + ExpectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + AnnualIncomeRange: + type: string + enum: + - UNDER_50K + - RANGE_50K_100K + - RANGE_100K_250K + - RANGE_250K_1M + - OVER_1M + description: Bucketed annual income (USD equivalent). Used for enhanced due diligence on higher-risk profiles. + example: RANGE_100K_250K + NetWorthRange: + type: string + enum: + - UNDER_100K + - RANGE_100K_500K + - RANGE_500K_1M + - RANGE_1M_5M + - RANGE_5M_25M + - OVER_25M + description: Bucketed total net worth (USD equivalent). Used for enhanced due diligence on higher-risk profiles. + example: RANGE_500K_1M + PepStatus: + type: string + enum: + - NONE + - DOMESTIC + - FOREIGN + - HIO + - FAMILY_OR_ASSOCIATE + description: Political exposure declaration (Politically Exposed Person status). `HIO` = head of an international organization. `FAMILY_OR_ASSOCIATE` covers close family members and known close associates of a PEP. + example: NONE + CustomerEdd: type: object - required: - - customerType + description: Enhanced-due-diligence (EDD) fields available as optional patchable attributes on an individual customer. Referenced via `allOf` from `IndividualCustomerFields`, so these appear as top-level optional fields on the customer resource itself; there is no separate EDD resource. The specific set required for a given customer is driven by the KYC provider's per-jurisdiction / per-flow / per-volume-tier rules (surfaced through `MISSING_FIELD` errors on `POST /verifications`). properties: - customerType: - type: string - enum: - - INDIVIDUAL - kycStatus: - $ref: '#/components/schemas/KycStatus' - fullName: + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/IndividualSourceOfFundsCategory' + description: Structured source-of-funds categories (FLOW of funds for this account). + example: + - SALARY + sourceOfFundsOtherDescription: type: string - description: Individual's full name - example: John Michael Doe - birthDate: + description: Free-form description of the customer's source of funds. Required when `sourceOfFundsCategories` includes `OTHER`; otherwise omitted. + example: Contest winnings + sourceOfWealthCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfWealthCategory' + description: Structured source-of-wealth categories (STOCK — origin of accumulated wealth). + example: + - SALARY + - INVESTMENTS + sourceOfWealthOtherDescription: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1990-01-15' - nationality: + description: Free-form description of the customer's source of wealth. Required when `sourceOfWealthCategories` includes `OTHER`; otherwise omitted. + example: Royalty income from published works + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + purposeOfAccountOtherDescription: type: string - description: Country code (ISO 3166-1 alpha-2) - example: US - address: - $ref: '#/components/schemas/Address' + description: Free-form description of the customer's intended purpose for the Grid account. Required when `purposeOfAccount` is `OTHER`; otherwise omitted. + example: Household budgeting between spouses + expectedMonthlyTransactionCount: + $ref: '#/components/schemas/ExpectedMonthlyTransactionCount' + expectedMonthlyTransactionVolume: + $ref: '#/components/schemas/ExpectedMonthlyTransactionVolume' + annualIncomeRange: + $ref: '#/components/schemas/AnnualIncomeRange' + netWorthRange: + $ref: '#/components/schemas/NetWorthRange' + pepStatus: + $ref: '#/components/schemas/PepStatus' + IndividualCustomerFields: + allOf: + - type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - INDIVIDUAL + kycStatus: + $ref: '#/components/schemas/KycStatus' + fullName: + type: string + description: Individual's full name + example: John Michael Doe + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1990-01-15' + nationality: + type: string + description: Country code (ISO 3166-1 alpha-2) + example: US + address: + $ref: '#/components/schemas/Address' + taxIdType: + $ref: '#/components/schemas/IdentificationType' + taxIdentifier: + type: string + description: Tax-identification number. For US persons this is the SSN (format `###-##-####`) or ITIN. For non-US persons this is the tax number issued by `taxIdCountryOfIssuance`. + example: 123-45-6789 + taxIdCountryOfIssuance: + type: string + description: Country that issued the tax identifier (ISO 3166-1 alpha-2). Required when `taxIdType` is `NON_US_TAX_ID`. + example: US + - $ref: '#/components/schemas/CustomerEdd' IndividualCustomer: title: Individual Customer allOf: @@ -10061,6 +12377,10 @@ components: - CORPORATION - S_CORPORATION - NON_PROFIT + - PUBLICLY_LISTED_COMPANY + - TRUST + - PRIVATE_FOUNDATION + - CHARITY - OTHER description: Legal entity type of the business example: LLC @@ -10088,24 +12408,6 @@ components: - ACCOMMODATION_AND_FOOD_SERVICES - OTHER_SERVICES - PUBLIC_ADMINISTRATION - PurposeOfAccount: - type: string - enum: - - CONTRACTOR_PAYOUTS - - CREATOR_PAYOUTS - - EMPLOYEE_PAYOUTS - - MARKETPLACE_SELLER_PAYOUTS - - SUPPLIER_PAYMENTS - - CROSS_BORDER_B2B - - AR_AUTOMATION - - AP_AUTOMATION - - EMBEDDED_PAYMENTS - - PLATFORM_FEE_COLLECTION - - P2P_TRANSFERS - - CHARITABLE_DONATIONS - - OTHER - description: The intended purpose for using the Grid account - example: CONTRACTOR_PAYOUTS SourceOfFundsCategory: type: string enum: @@ -10120,7 +12422,131 @@ components: example: OPERATING_REVENUE BusinessInfoUpdate: type: object - description: Additional information for business entities + description: Additional information for business entities + properties: + legalName: + type: string + description: Legal name of the business + example: Acme Corporation, Inc. + doingBusinessAs: + type: string + description: Trade name or DBA name of the business, if different from the legal name + example: Acme + country: + type: string + description: Country of incorporation or registration (ISO 3166-1 alpha-2) + example: US + registrationNumber: + type: string + description: Business registration number + example: '5523041' + incorporatedOn: + type: string + format: date + description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) + example: '2018-03-14' + entityType: + $ref: '#/components/schemas/EntityType' + taxId: + type: string + description: Tax identification number + example: 47-1234567 + countriesOfOperation: + type: array + items: + type: string + description: List of countries where the business operates (ISO 3166-1 alpha-2) + example: + - US + businessType: + $ref: '#/components/schemas/BusinessType' + purposeOfAccount: + $ref: '#/components/schemas/PurposeOfAccount' + sourceOfFunds: + type: string + description: The primary source of funds for the business + example: Funds derived from customer payments for software services + expectedMonthlyTransactionCount: + type: string + enum: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BusinessCustomerFields: + type: object + required: + - customerType + properties: + customerType: + type: string + enum: + - BUSINESS + kybStatus: + $ref: '#/components/schemas/KybStatus' + address: + $ref: '#/components/schemas/Address' + businessInfo: + $ref: '#/components/schemas/BusinessInfoUpdate' + BusinessInfoResponse: + type: object + description: | + Business information returned on a customer. `taxId` and `incorporatedOn` are + required on creation but may be absent on legacy customers that pre-date the + requirement, so both are optional in responses. + required: + - legalName properties: legalName: type: string @@ -10167,84 +12593,300 @@ components: expectedMonthlyTransactionCount: type: string enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - COUNT_UNDER_10 + - COUNT_10_TO_100 + - COUNT_100_TO_500 + - COUNT_500_TO_1000 + - COUNT_OVER_1000 + description: Expected number of transactions per month + example: COUNT_100_TO_500 + expectedMonthlyTransactionVolume: + type: string + enum: + - VOLUME_UNDER_10K + - VOLUME_10K_TO_100K + - VOLUME_100K_TO_1M + - VOLUME_1M_TO_10M + - VOLUME_OVER_10M + description: Expected total transaction volume per month in USD equivalent + example: VOLUME_100K_TO_1M + expectedRecipientJurisdictions: + type: array + items: + type: string + description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) + example: + - US + naicsCode: + type: string + pattern: ^\d{2,6}$ + description: NAICS code describing the nature of the business (2-6 digits) + example: '541511' + sourceOfFundsCategories: + type: array + items: + $ref: '#/components/schemas/SourceOfFundsCategory' + description: Structured source-of-funds categories for the business + example: + - OPERATING_REVENUE + sourceOfFundsOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the source of funds when OTHER is selected + example: Proceeds from a legal settlement + purposeOfAccountOtherDescription: + type: string + minLength: 1 + maxLength: 500 + description: Description of the account purpose when OTHER is selected + example: Escrow for equipment leases + expectedCounterpartyCountries: + type: array + items: + type: string + description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + example: + - US + BeneficialOwnerRole: + type: string + enum: + - UBO + - DIRECTOR + - COMPANY_OFFICER + - CONTROL_PERSON + - TRUSTEE + - GENERAL_PARTNER + description: Role of the beneficial owner within the business + example: UBO + BeneficialOwnerPersonalInfo: + type: object + required: + - firstName + - lastName + - birthDate + - nationality + - address + - idType + - identifier + properties: + firstName: + type: string + description: First name of the individual + example: Jane + middleName: + type: string + description: Middle name of the individual + example: Marie + lastName: + type: string + description: Last name of the individual + example: Smith + birthDate: + type: string + format: date + description: Date of birth in ISO 8601 format (YYYY-MM-DD) + example: '1978-06-15' + nationality: + type: string + description: Country of nationality (ISO 3166-1 alpha-2) + example: US + email: + type: string + format: email + description: Email address of the individual + example: jane.smith@acmecorp.com + phoneNumber: + type: string + description: Phone number in E.164 format + example: '+14155550192' + pattern: ^\+[1-9]\d{1,14}$ + address: + $ref: '#/components/schemas/Address' + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + description: The identification number or value + example: 123-45-6789 + countryOfIssuance: + type: string + description: Country that issued the identification (ISO 3166-1 alpha-2) + example: US + BeneficialOwner: + type: object + required: + - id + - customerId + - roles + - ownershipPercentage + - personalInfo + - kycStatus + - createdAt + properties: + id: + type: string + description: Unique identifier for this beneficial owner + example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 + customerId: + type: string + description: The ID of the business customer this beneficial owner is associated with + example: Customer:019542f5-b3e7-1d02-0000-000000000001 + roles: + type: array + items: + $ref: '#/components/schemas/BeneficialOwnerRole' + description: Roles of this person within the business + example: + - UBO + - DIRECTOR + ownershipPercentage: + type: integer + description: Percentage of ownership in the business (0-100) + minimum: 0 + maximum: 100 + example: 51 + personalInfo: + $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' + kycStatus: + $ref: '#/components/schemas/KycStatus' + createdAt: + type: string + format: date-time + description: When this beneficial owner was created + example: '2025-10-03T12:00:00Z' + updatedAt: + type: string + format: date-time + description: When this beneficial owner was last updated + example: '2025-10-03T12:00:00Z' + BusinessCustomer: + title: Business Customer + allOf: + - $ref: '#/components/schemas/Customer' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfoResponse' + beneficialOwners: + type: array + items: + $ref: '#/components/schemas/BeneficialOwner' + CustomerOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomer' + - $ref: '#/components/schemas/BusinessCustomer' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomer' + BUSINESS: '#/components/schemas/BusinessCustomer' + CustomerListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + description: List of customers matching the filter criteria + items: + $ref: '#/components/schemas/CustomerOneOf' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: + type: string + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of customers matching the criteria (excluding pagination) + Error405: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 405 + description: HTTP status code + code: type: string + description: | + | Error Code | Description | + |------------|-------------| + | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + - METHOD_NOT_ALLOWED + message: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerCreateRequest: + type: object + required: + - customerType + properties: + platformCustomerId: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + description: Platform-specific customer identifier. If not provided, one will be generated by the system. + example: 9f84e0c2a72c4fa + customerType: + $ref: '#/components/schemas/CustomerType' + region: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: + description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. + example: US + currencies: type: array items: type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) + description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. example: - - US - BusinessCustomerFields: - type: object - required: - - customerType - properties: - customerType: + - USD + - USDC + email: type: string - enum: - - BUSINESS - kybStatus: - $ref: '#/components/schemas/KybStatus' - address: - $ref: '#/components/schemas/Address' - businessInfo: - $ref: '#/components/schemas/BusinessInfoUpdate' - BusinessInfoResponse: + format: email + description: Email address for the customer. **Required in regions that verify the email address before identity verification** (e.g. the EU); optional otherwise. + example: john.doe@example.com + phoneNumber: + type: string + pattern: ^\+[1-9]\d{1,14}$ + description: Phone number for the customer in strict E.164 format. **Required in regions that verify the phone number before identity verification** (e.g. the EU); optional otherwise. + example: '+14155551234' + umaAddress: + type: string + description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerCreateRequest: + title: Individual Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + - type: object + properties: + idType: + $ref: '#/components/schemas/IdentificationType' + identifier: + type: string + writeOnly: true + description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. + example: 123-45-6789 + BusinessInfo: type: object - description: | - Business information returned on a customer. `taxId` and `incorporatedOn` are - required on creation but may be absent on legacy customers that pre-date the - requirement, so both are optional in responses. + description: Additional information required for business entities required: - legalName + - taxId + - incorporatedOn properties: legalName: type: string @@ -10346,454 +12988,641 @@ components: description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) example: - US - BeneficialOwnerRole: - type: string - enum: - - UBO - - DIRECTOR - - COMPANY_OFFICER - - CONTROL_PERSON - - TRUSTEE - - GENERAL_PARTNER - description: Role of the beneficial owner within the business - example: UBO - IdentificationType: - type: string - enum: - - SSN - - ITIN - - EIN - - NON_US_TAX_ID - description: Type of tax identification - example: SSN - BeneficialOwnerPersonalInfo: + BusinessCustomerCreateRequest: + title: Business Customer Create Request + allOf: + - $ref: '#/components/schemas/CustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + - type: object + required: + - businessInfo + properties: + businessInfo: + $ref: '#/components/schemas/BusinessInfo' + CustomerCreateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerCreateRequest' + - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' + Error409: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 409 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | + | TRANSACTION_NOT_CANCELLABLE | Transaction has already settled or is otherwise past the point where it can be cancelled | + | UMA_ADDRESS_EXISTS | UMA address already exists | + | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | + | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | + | PASSKEY_ALREADY_ENROLLED | The customer already has an enrolled passkey factor; only one passkey per customer is supported. Delete the existing one before enrolling another | + | SCA_SESSION_REQUIRED | The customer's Strong Customer Authentication login session is missing or expired. Re-authenticate the customer, then retry the request. Distinct from a `401`, which means the platform's own API credentials were rejected | + | BENEFICIARY_TRUSTED | The external account is currently a trusted beneficiary, so it cannot be deleted. Untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete | + | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | + enum: + - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL + - TRANSACTION_NOT_CANCELLABLE + - UMA_ADDRESS_EXISTS + - EMAIL_OTP_EMAIL_ALREADY_EXISTS + - EMAIL_OTP_CREDENTIAL_SET_CHANGED + - PASSKEY_ALREADY_ENROLLED + - SCA_SESSION_REQUIRED + - BENEFICIARY_TRUSTED + - CONFLICT + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error404: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 404 + description: HTTP status code + code: + type: string + description: | + | Error Code | Description | + |------------|-------------| + | TRANSACTION_NOT_FOUND | Transaction not found | + | INVITATION_NOT_FOUND | Invitation not found | + | USER_NOT_FOUND | Customer not found | + | QUOTE_NOT_FOUND | Quote not found | + | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | + | TOKEN_NOT_FOUND | Token not found | + | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | + | REFERENCE_NOT_FOUND | Reference not found | + | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | + | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | + enum: + - TRANSACTION_NOT_FOUND + - INVITATION_NOT_FOUND + - USER_NOT_FOUND + - QUOTE_NOT_FOUND + - LOOKUP_REQUEST_NOT_FOUND + - TOKEN_NOT_FOUND + - BULK_UPLOAD_JOB_NOT_FOUND + - REFERENCE_NOT_FOUND + - UMA_NOT_FOUND + - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + Error410: type: object required: - - firstName - - lastName - - birthDate - - nationality - - address - - idType - - identifier + - message + - status + - code properties: - firstName: - type: string - description: First name of the individual - example: Jane - middleName: - type: string - description: Middle name of the individual - example: Marie - lastName: - type: string - description: Last name of the individual - example: Smith - birthDate: + status: + type: integer + enum: + - 410 + description: HTTP status code + code: type: string - format: date - description: Date of birth in ISO 8601 format (YYYY-MM-DD) - example: '1978-06-15' - nationality: + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | + enum: + - CUSTOMER_DELETED + message: type: string - description: Country of nationality (ISO 3166-1 alpha-2) - example: US + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + CustomerUpdateRequest: + title: Customer Update Request + description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. + type: object + required: + - customerType + properties: + customerType: + $ref: '#/components/schemas/CustomerType' + currencies: + type: array + items: + type: string + description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. + example: + - USD + - EUR + - USDC email: type: string format: email - description: Email address of the individual - example: jane.smith@acmecorp.com + description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. + example: john.doe@example.com phoneNumber: type: string - description: Phone number in E.164 format - example: '+14155550192' pattern: ^\+[1-9]\d{1,14}$ - address: - $ref: '#/components/schemas/Address' - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - description: The identification number or value - example: 123-45-6789 - countryOfIssuance: + description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. + example: '+14155551234' + umaAddress: type: string - description: Country that issued the identification (ISO 3166-1 alpha-2) - example: US - BeneficialOwner: + description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. + example: $john.doe@uma.domain.com + IndividualCustomerUpdateRequest: + title: Individual Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/IndividualCustomerFields' + BusinessCustomerUpdateRequest: + title: Business Customer Update Request + allOf: + - $ref: '#/components/schemas/CustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerFields' + CustomerUpdateRequestOneOf: + oneOf: + - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' + - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' + discriminator: + propertyName: customerType + mapping: + INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' + BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + SignedRequestChallenge: + title: Signed Request Challenge type: object required: - - id - - customerId - - roles - - ownershipPercentage - - personalInfo - - kycStatus - - createdAt + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. properties: - id: + payloadToSign: type: string - description: Unique identifier for this beneficial owner - example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001 - customerId: + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' + requestId: type: string - description: The ID of the business customer this beneficial owner is associated with - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - roles: - type: array - items: - $ref: '#/components/schemas/BeneficialOwnerRole' - description: Roles of this person within the business - example: - - UBO - - DIRECTOR - ownershipPercentage: - type: integer - description: Percentage of ownership in the business (0-100) - minimum: 0 - maximum: 100 - example: 51 - personalInfo: - $ref: '#/components/schemas/BeneficialOwnerPersonalInfo' - kycStatus: - $ref: '#/components/schemas/KycStatus' - createdAt: + description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: type: string format: date-time - description: When this beneficial owner was created - example: '2025-10-03T12:00:00Z' - updatedAt: + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' + Error424: + type: object + required: + - message + - status + - code + properties: + status: + type: integer + enum: + - 424 + description: HTTP status code + code: type: string - format: date-time - description: When this beneficial owner was last updated - example: '2025-10-03T12:00:00Z' - BusinessCustomer: - title: Business Customer - allOf: - - $ref: '#/components/schemas/Customer' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfoResponse' - beneficialOwners: - type: array - items: - $ref: '#/components/schemas/BeneficialOwner' - CustomerOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomer' - - $ref: '#/components/schemas/BusinessCustomer' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomer' - BUSINESS: '#/components/schemas/BusinessCustomer' - CustomerListResponse: + description: | + | Error Code | Description | + |------------|-------------| + | PAYREQ_REQUEST_FAILED | Payment request failed | + | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | + | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | + | LNURLP_REQUEST_FAILED | LNURLP request failed | + | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | + enum: + - PAYREQ_REQUEST_FAILED + - COUNTERPARTY_PUBKEY_FETCH_ERROR + - NO_COMPATIBLE_UMA_VERSION + - LNURLP_REQUEST_FAILED + - EMAIL_OTP_CREDENTIAL_SYNC_FAILED + message: + type: string + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + KycLinkCreateRequest: + type: object + description: Request body for generating a hosted KYC link for an existing customer. + properties: + redirectUri: + type: string + format: uri + description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. + example: https://app.example.com/onboarding/completed + KycProvider: + type: string + description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. + enum: + - SUMSUB + example: SUMSUB + KycLinkResponse: type: object + description: A hosted KYC link that the customer can complete to verify their identity. required: - - data - - hasMore + - kycUrl + - expiresAt + - provider properties: - data: - type: array - description: List of customers matching the filter criteria - items: - $ref: '#/components/schemas/CustomerOneOf' - hasMore: - type: boolean - description: Indicates if more results are available beyond this page - nextCursor: + kycUrl: type: string - description: Cursor to retrieve the next page of results (only present if hasMore is true) - totalCount: - type: integer - description: Total number of customers matching the criteria (excluding pagination) - Error405: + description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. + example: https://kyc.lightspark.com/onboard/abc123def456 + expiresAt: + type: string + format: date-time + description: Time at which the hosted link expires and can no longer be used. + example: '2027-01-15T14:32:00Z' + provider: + $ref: '#/components/schemas/KycProvider' + token: + type: string + description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. + example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ContactVerificationConfirmRequest: type: object + description: Request body for confirming an email or phone verification challenge. required: - - message - - status - code properties: - status: - type: integer - enum: - - 405 - description: HTTP status code code: type: string - description: | - | Error Code | Description | - |------------|-------------| - | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint | - enum: - - METHOD_NOT_ALLOWED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - CustomerCreateRequest: + description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. + example: '123456' + ScaFactor: + type: string + enum: + - SMS_OTP + - TOTP + - PASSKEY + description: | + A Strong Customer Authentication factor. + + | Factor | Description | + |--------|-------------| + | `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. | + | `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). | + | `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. | + ScaFactorView: type: object + description: An enrolled Strong Customer Authentication factor. required: - - customerType + - factor properties: - platformCustomerId: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The kind of enrolled factor. + credentialId: type: string - description: Platform-specific customer identifier. If not provided, one will be generated by the system. - example: 9f84e0c2a72c4fa - customerType: - $ref: '#/components/schemas/CustomerType' - region: + description: The per-credential id, populated only for `PASSKEY` factors (the id passed to delete a passkey). Omitted for `TOTP` and `SMS_OTP`, which have no per-credential id. + name: type: string - description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity. This determines the regulatory jurisdiction and KYC requirements for the customer. Required if the customer will use currencies with different KYC requirements across regions. A customer with accounts in multiple regions should be registered as separate customers. This field is immutable after creation. - example: US - currencies: + description: An optional human-readable label for this factor. + ScaFactorList: + type: object + description: The Strong Customer Authentication factors a customer has enrolled. + required: + - factors + properties: + factors: type: array + description: The customer's enrolled SCA factors. items: - type: string - description: List of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Required if the customer will use more than one sending currency, since the correct currencies cannot always be inferred. If not provided, currencies will be inferred from the customer's region. Some currency combinations may require separate customers — if so, the request will be rejected with details. - example: - - USD - - USDC - email: - type: string - format: email - description: Email address for the customer. - example: john.doe@example.com - phoneNumber: - type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. - example: '+14155551234' - umaAddress: + $ref: '#/components/schemas/ScaFactorView' + TotpFactorEnrollRequest: + type: object + title: TOTP Factor Enroll Request + description: Start enrolling a time-based one-time-password (TOTP) authenticator factor. + required: + - type + properties: + type: type: string - description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerCreateRequest: - title: Individual Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - - type: object - properties: - idType: - $ref: '#/components/schemas/IdentificationType' - identifier: - type: string - writeOnly: true - description: The individual's tax identification number. Required to onboard the individual as a US account holder. Only SSN and ITIN are currently accepted for an individual account holder; other identification types are rejected. Write-only — never returned in customer responses. - example: 123-45-6789 - BusinessInfo: + enum: + - TOTP + description: Discriminator selecting the TOTP factor. TOTP enrollment needs no other input at start. + PasskeyFactorEnrollRequest: type: object - description: Additional information required for business entities + title: Passkey Factor Enroll Request + description: Start enrolling a WebAuthn passkey factor. required: - - legalName - - taxId - - incorporatedOn + - type properties: - legalName: + type: type: string - description: Legal name of the business - example: Acme Corporation, Inc. - doingBusinessAs: + enum: + - PASSKEY + description: Discriminator selecting the passkey factor. Passkey enrollment needs no other input at start. + ScaFactorEnrollRequestOneOf: + oneOf: + - $ref: '#/components/schemas/TotpFactorEnrollRequest' + - $ref: '#/components/schemas/PasskeyFactorEnrollRequest' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpFactorEnrollRequest' + PASSKEY: '#/components/schemas/PasskeyFactorEnrollRequest' + description: Which SCA factor to begin enrolling, selected by `type`. `SMS_OTP` is not enrollable (it uses the customer's verified phone), so only `TOTP` and `PASSKEY` are valid here. + TotpEnrollmentStart: + type: object + description: The shared secret a customer's authenticator app needs to enroll a TOTP factor. Returned by `POST /sca/factors` for a `TOTP` request; the customer scans `totpUri` (an `otpauth://` provisioning URI) and confirms with the first code their app produces. + required: + - type + - secret + - secretBase32Encoded + - totpUri + properties: + type: type: string - description: Trade name or DBA name of the business, if different from the legal name - example: Acme - country: + enum: + - TOTP + description: Discriminator identifying this as the TOTP enrollment-start payload. + secret: type: string - description: Country of incorporation or registration (ISO 3166-1 alpha-2) - example: US - registrationNumber: + description: The raw TOTP shared secret. + secretBase32Encoded: type: string - description: Business registration number - example: '5523041' - incorporatedOn: + description: The Base32-encoded shared secret, suitable for manual entry into an authenticator app that does not scan QR codes. + totpUri: type: string - format: date - description: Date of incorporation in ISO 8601 format (YYYY-MM-DD) - example: '2018-03-14' - entityType: - $ref: '#/components/schemas/EntityType' - taxId: + description: The `otpauth://` provisioning URI (the QR-code payload) the customer's authenticator app scans to enroll the factor. + example: otpauth://totp/Grid:customer@example.com?secret=ABC123&issuer=Grid + PasskeyEnrollmentStart: + type: object + description: Opaque WebAuthn registration options relayed to the end user's device to enroll a passkey factor. Grid performs no crypto; pass `options` to the device's WebAuthn API to produce a credential, then submit that credential to the confirm endpoint unmodified. + required: + - type + - options + - allowedOrigins + - relyingPartyId + properties: + type: type: string - description: Tax identification number - example: 47-1234567 - countriesOfOperation: + enum: + - PASSKEY + description: Discriminator identifying this as the passkey enrollment-start payload. + options: + type: object + additionalProperties: true + description: Opaque WebAuthn `PublicKeyCredentialCreationOptions`. Pass to the device's WebAuthn registration API unmodified. + allowedOrigins: type: array + description: The origins the WebAuthn registration ceremony may run against. The origin the credential is produced against must be one of these and must be echoed back on the confirm call. items: type: string - description: List of countries where the business operates (ISO 3166-1 alpha-2) example: - - US - businessType: - $ref: '#/components/schemas/BusinessType' - purposeOfAccount: - $ref: '#/components/schemas/PurposeOfAccount' - sourceOfFunds: + - https://app.example.com + relyingPartyId: type: string - description: The primary source of funds for the business - example: Funds derived from customer payments for software services - expectedMonthlyTransactionCount: + description: The WebAuthn relying-party id the credential is bound to. + example: app.example.com + ScaFactorEnrollStartOneOf: + oneOf: + - $ref: '#/components/schemas/TotpEnrollmentStart' + - $ref: '#/components/schemas/PasskeyEnrollmentStart' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpEnrollmentStart' + PASSKEY: '#/components/schemas/PasskeyEnrollmentStart' + description: 'The factor-specific material needed to complete enrollment, keyed by `type`: a TOTP shared secret + provisioning URI, or the WebAuthn registration options for a passkey.' + TotpEnrollmentConfirmRequest: + type: object + description: The shared secret returned by the TOTP enrollment start, plus the first code the customer's authenticator app produces, submitted to confirm and finalize the TOTP factor. + required: + - type + - secret + - code + properties: + type: type: string enum: - - COUNT_UNDER_10 - - COUNT_10_TO_100 - - COUNT_100_TO_500 - - COUNT_500_TO_1000 - - COUNT_OVER_1000 - description: Expected number of transactions per month - example: COUNT_100_TO_500 - expectedMonthlyTransactionVolume: + - TOTP + description: Discriminator selecting the TOTP confirm variant. + secret: type: string - enum: - - VOLUME_UNDER_10K - - VOLUME_10K_TO_100K - - VOLUME_100K_TO_1M - - VOLUME_1M_TO_10M - - VOLUME_OVER_10M - description: Expected total transaction volume per month in USD equivalent - example: VOLUME_100K_TO_1M - expectedRecipientJurisdictions: - type: array - items: - type: string - description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2) - example: - - US - naicsCode: + description: The shared secret returned as `secret` by the TOTP enrollment start, threaded back to bind the confirmation to that enrollment. + code: type: string - pattern: ^\d{2,6}$ - description: NAICS code describing the nature of the business (2-6 digits) - example: '541511' - sourceOfFundsCategories: - type: array - items: - $ref: '#/components/schemas/SourceOfFundsCategory' - description: Structured source-of-funds categories for the business - example: - - OPERATING_REVENUE - sourceOfFundsOtherDescription: + description: The current time-based one-time code from the customer's authenticator app. In sandbox, the code is always `123456`. + example: '123456' + PasskeyEnrollmentConfirmRequest: + type: object + description: The WebAuthn credential a device produced for a passkey registration challenge, submitted to enroll the passkey factor. + required: + - type + - origin + - credential + properties: + type: type: string - minLength: 1 - maxLength: 500 - description: Description of the source of funds when OTHER is selected - example: Proceeds from a legal settlement - purposeOfAccountOtherDescription: + enum: + - PASSKEY + description: Discriminator selecting the passkey confirm variant. + origin: type: string - minLength: 1 - maxLength: 500 - description: Description of the account purpose when OTHER is selected - example: Escrow for equipment leases - expectedCounterpartyCountries: - type: array - items: - type: string - description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2) - example: - - US - BusinessCustomerCreateRequest: - title: Business Customer Create Request - allOf: - - $ref: '#/components/schemas/CustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - - type: object - properties: - businessInfo: - $ref: '#/components/schemas/BusinessInfo' - CustomerCreateRequestOneOf: + description: The WebAuthn origin the `credential` was produced against (one of the enrollment start's `allowedOrigins`). + example: https://app.example.com + credential: + type: object + additionalProperties: true + description: Opaque WebAuthn credential the device produced from the enrollment start's `options`. + ScaFactorConfirmRequestOneOf: oneOf: - - $ref: '#/components/schemas/IndividualCustomerCreateRequest' - - $ref: '#/components/schemas/BusinessCustomerCreateRequest' + - $ref: '#/components/schemas/TotpEnrollmentConfirmRequest' + - $ref: '#/components/schemas/PasskeyEnrollmentConfirmRequest' discriminator: - propertyName: customerType + propertyName: type mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest' - Error409: + TOTP: '#/components/schemas/TotpEnrollmentConfirmRequest' + PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmRequest' + description: 'The proof that finalizes enrollment, keyed by `type`: the TOTP shared secret + code, or the passkey `origin` + `credential`.' + TotpEnrollmentConfirmResponse: type: object + description: The one-time recovery codes issued once a TOTP factor is enrolled. These are shown to the customer only once; store them somewhere safe to recover access if the authenticator device is lost. required: - - message - - status - - code + - type + - recoveryCodes properties: - status: - type: integer - enum: - - 409 - description: HTTP status code - code: + type: type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval | - | UMA_ADDRESS_EXISTS | UMA address already exists | - | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential | - | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued | - | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform | enum: - - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL - - UMA_ADDRESS_EXISTS - - EMAIL_OTP_EMAIL_ALREADY_EXISTS - - EMAIL_OTP_CREDENTIAL_SET_CHANGED - - CONFLICT - message: + - TOTP + description: Discriminator identifying this as the TOTP enrollment result. + recoveryCodes: + type: array + description: The one-time recovery codes for this TOTP factor. + items: + type: string + example: + - ABCD-EFGH-IJKL + - MNOP-QRST-UVWX + PasskeyEnrollmentConfirmResponse: + type: object + description: The enrolled passkey factor returned after a successful confirmation. + required: + - type + - factor + properties: + type: type: string - description: Error message - details: - type: object - description: Additional error details + enum: + - PASSKEY + description: Discriminator identifying this as the passkey enrollment result. + factor: + $ref: '#/components/schemas/ScaFactorView' + ScaFactorConfirmResponseOneOf: + oneOf: + - $ref: '#/components/schemas/TotpEnrollmentConfirmResponse' + - $ref: '#/components/schemas/PasskeyEnrollmentConfirmResponse' + discriminator: + propertyName: type + mapping: + TOTP: '#/components/schemas/TotpEnrollmentConfirmResponse' + PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmResponse' + description: The enrollment result, keyed by `type`. + ScaLoginStartRequest: + type: object + description: Selects which enrolled factor to start an SCA login with. The factor must already be enrolled (or, for `SMS_OTP`, the phone verified). + required: + - factor + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor to authenticate with. + ScaLoginStart: + type: object + description: 'The factor-specific material a customer needs to complete an SCA login. Each factor surfaces only the fields it issues: `SMS_OTP` carries `challengeId` and `expiresAt`; `TOTP` carries neither (the customer reads the code from their authenticator app); `PASSKEY` carries the opaque WebAuthn `passkeyOptions` with `allowedOrigins` and `relyingPartyId`.' + required: + - factor + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor this login was started for. + challengeId: + type: + - string + - 'null' + description: The challenge handle for an `SMS_OTP` login, threaded back on the complete call. Present only for `SMS_OTP`. + expiresAt: + type: + - string + - 'null' + format: date-time + description: Absolute UTC timestamp after which the `SMS_OTP` code expires. Present only for `SMS_OTP`. + example: '2025-10-03T12:05:00Z' + passkeyOptions: + type: + - object + - 'null' additionalProperties: true - Error404: + description: Opaque WebAuthn assertion request options. Present only for `PASSKEY`; pass to the device's WebAuthn API to produce the assertion submitted on the complete call. + allowedOrigins: + type: + - array + - 'null' + items: + type: string + description: The origins the WebAuthn ceremony may run against. Present only for `PASSKEY`. + example: + - https://app.example.com + relyingPartyId: + type: + - string + - 'null' + description: The WebAuthn relying-party id. Present only for `PASSKEY`. + example: app.example.com + ScaLoginCompleteRequest: type: object + description: Completes an SCA login by submitting the proof for the started factor. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`), plus the `factor` being completed, the `endUserIpAddress` the login is being performed from, and, for `SMS_OTP`, the `challengeId` returned by the login start. + required: + - factor + - endUserIpAddress + anyOf: + - required: + - code + - required: + - passkeyAssertion + - origin + properties: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor being completed; must match the started login. + endUserIpAddress: + type: string + description: The IP address of the end user's device completing this login, recorded against the login event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and any transaction-risk exemption. + example: 203.0.113.42 + challengeId: + type: + - string + - 'null' + description: The challenge handle returned by the login start, required for `SMS_OTP` and omitted for other factors. + code: + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. + example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the login start's `passkeyOptions`. Required when completing a `PASSKEY` login. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against (one of the login start's `allowedOrigins`). Required alongside `passkeyAssertion`; omit it for the `code` path. + example: https://app.example.com + ScaLoginComplete: + type: object + description: The status of a completed SCA login session. required: - - message - status - - code properties: status: - type: integer - enum: - - 404 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | TRANSACTION_NOT_FOUND | Transaction not found | - | INVITATION_NOT_FOUND | Invitation not found | - | USER_NOT_FOUND | Customer not found | - | QUOTE_NOT_FOUND | Quote not found | - | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found | - | TOKEN_NOT_FOUND | Token not found | - | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found | - | REFERENCE_NOT_FOUND | Reference not found | - | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP | - | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found | - enum: - - TRANSACTION_NOT_FOUND - - INVITATION_NOT_FOUND - - USER_NOT_FOUND - - QUOTE_NOT_FOUND - - LOOKUP_REQUEST_NOT_FOUND - - TOKEN_NOT_FOUND - - BULK_UPLOAD_JOB_NOT_FOUND - - REFERENCE_NOT_FOUND - - UMA_NOT_FOUND - - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND - message: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - Error410: + description: The status of the login session. A successful login reports `SUCCESS`; other values indicate the login did not complete and should be surfaced to the caller. + example: SUCCESS + sessionExpiresAt: + type: + - string + - 'null' + format: date-time + description: Absolute UTC timestamp after which the customer's SCA session is no longer valid and they must complete another SCA login. Money movement in SCA-regulated currencies is refused once it passes, so prompt a re-login ahead of it rather than waiting for a `SCA_SESSION_REQUIRED` failure. Present when the login established a session. + example: '2026-01-29T12:00:00Z' + Error423: type: object required: - message @@ -10803,97 +13632,117 @@ components: status: type: integer enum: - - 410 + - 423 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | CUSTOMER_DELETED | Customer has been permanently deleted | + | ACCOUNT_LOCKED | The customer's login is temporarily locked (or suspended) after too many failed attempts. Inspect `details.lockedUntil` for when the customer may retry, and `details.failedAttempts` for the current count. | enum: - - CUSTOMER_DELETED + - ACCOUNT_LOCKED message: type: string description: Error message details: type: object - description: Additional error details + description: Lockout detail. `lockedUntil` is the UTC timestamp the customer may retry after (absent when suspended indefinitely); `failedAttempts` is the current cumulative failed-login count; `suspended` is true when the account is locked with no automatic expiry. + properties: + suspended: + type: boolean + lockedUntil: + type: + - string + - 'null' + format: date-time + failedAttempts: + type: integer additionalProperties: true - CustomerUpdateRequest: - title: Customer Update Request - description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls. + RecordSecurityEventRequest: type: object + description: Records a client-side security event for the customer so Grid can maintain the customer's login-security state (SCA session revocation and failed-login lockout). required: - - customerType + - eventType properties: - customerType: - $ref: '#/components/schemas/CustomerType' - currencies: - type: array - items: - type: string - description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details. - example: - - USD - - EUR - - USDC - email: - type: string - format: email - description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets. - example: john.doe@example.com - phoneNumber: + eventType: type: string - pattern: ^\+[1-9]\d{1,14}$ - description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls. - example: '+14155551234' - umaAddress: + enum: + - RESET_PASSWORD_COMPLETED + - FAILED_LOGIN_ATTEMPT + description: | + The security event to record: + + | Value | Effect | + |-------|--------| + | `RESET_PASSWORD_COMPLETED` | Revokes every active SCA session for the customer and clears the failed-login counter. | + | `FAILED_LOGIN_ATTEMPT` | Increments the cumulative failed-login counter and escalates the lockout at each milestone: 5 attempts → 15 minutes, 6 → 30 minutes, 7 → 1 hour, 8 → 24 hours, 9 or more → suspension. | + example: FAILED_LOGIN_ATTEMPT + RecordSecurityEventResponse: + type: object + description: The customer's login-security state after recording the event, so the integrator can surface a lockout to the end user. + required: + - eventType + - suspended + - failedAttempts + properties: + eventType: type: string - description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer. - example: $john.doe@uma.domain.com - IndividualCustomerUpdateRequest: - title: Individual Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/IndividualCustomerFields' - BusinessCustomerUpdateRequest: - title: Business Customer Update Request - allOf: - - $ref: '#/components/schemas/CustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerFields' - CustomerUpdateRequestOneOf: - oneOf: - - $ref: '#/components/schemas/IndividualCustomerUpdateRequest' - - $ref: '#/components/schemas/BusinessCustomerUpdateRequest' - discriminator: - propertyName: customerType - mapping: - INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' - BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - SignedRequestChallenge: - title: Signed Request Challenge + enum: + - RESET_PASSWORD_COMPLETED + - FAILED_LOGIN_ATTEMPT + description: The event that was recorded. + example: FAILED_LOGIN_ATTEMPT + suspended: + type: boolean + description: Whether the customer's login is currently suspended (locked with no automatic expiry). A suspended customer must go through a password reset (`RESET_PASSWORD_COMPLETED`) to clear the lockout. + lockedUntil: + type: + - string + - 'null' + format: date-time + description: When the customer may attempt to log in again, if temporarily locked. Null when not locked, or when `suspended` is true (no automatic expiry). + example: '2025-10-03T12:15:00Z' + failedAttempts: + type: integer + description: The customer's current cumulative failed-login count. + example: 5 + TwoFactorResetStartRequest: type: object + description: Selects which enrolled factor to reset via the liveness-gated recovery flow. required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. + - factor properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}' - requestId: + factor: + $ref: '#/components/schemas/ScaFactor' + description: The enrolled factor to reset. + TwoFactorResetStart: + type: object + description: The reset handle plus the opaque liveness handles a caller relays to the end-user device to complete the liveness check. `resetId` threads the ceremony together (status and complete reference it). `livenessAccessToken` and `verificationLink` are omitted when they are not returned. + required: + - resetId + properties: + resetId: type: string - description: Grid-issued `Request:` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + description: Identifier for this reset; pass it to the status and complete endpoints. + livenessAccessToken: + type: + - string + - 'null' + description: Access token for the embedded liveness/verification SDK, bound to this reset. Omitted when one is not returned. + verificationLink: + type: + - string + - 'null' + description: Hosted identity-verification page URL for completing liveness. Omitted when one is not returned. expiresAt: - type: string + type: + - string + - 'null' format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' - Error424: + description: Absolute UTC timestamp at the end of the reset window. Omitted when one is not returned. + example: '2025-10-03T12:30:00Z' + Error429: type: object required: - message @@ -10903,79 +13752,192 @@ components: status: type: integer enum: - - 424 + - 429 description: HTTP status code code: type: string description: | | Error Code | Description | |------------|-------------| - | PAYREQ_REQUEST_FAILED | Payment request failed | - | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key | - | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version | - | LNURLP_REQUEST_FAILED | LNURLP request failed | - | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials | + | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | enum: - - PAYREQ_REQUEST_FAILED - - COUNTERPARTY_PUBKEY_FETCH_ERROR - - NO_COMPATIBLE_UMA_VERSION - - LNURLP_REQUEST_FAILED - - EMAIL_OTP_CREDENTIAL_SYNC_FAILED + - RATE_LIMITED message: type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true - KycLinkCreateRequest: + description: Error message + details: + type: object + description: Additional error details + additionalProperties: true + TwoFactorResetStatus: + type: object + description: The status of an in-progress 2FA reset, polled until it reaches a terminal value. + required: + - status + - factor + - expiresAt + properties: + status: + type: string + enum: + - INITIATED + - PENDING_REVIEW + - LIVENESS_PASSED + - COMPLETED + - REJECTED + - EXPIRED + description: |- + The reset status. + | Value | Terminal | Meaning | |-------|----------|---------| | `INITIATED` | no | Reset started; liveness not yet submitted. Keep polling. | | `PENDING_REVIEW` | no | Liveness submitted; under review. Keep polling. | | `LIVENESS_PASSED` | no | Liveness passed; call the complete endpoint to finish the reset. | | `COMPLETED` | **yes** | Reset finished; the lost factor is cleared and re-enrollment can begin. | | `REJECTED` | **yes** | Liveness failed. Stop polling; start a new reset. | | `EXPIRED` | **yes** | The reset window closed before it completed. Stop polling; start a new reset. | + Stop polling on any terminal value. + example: INITIATED + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor being reset. + enrollmentStatus: + type: + - string + - 'null' + enum: + - PENDING + - COMPLETED + - null + description: The enrollment status of the replacement factor. `PENDING` until the customer finishes re-enrolling, then `COMPLETED`. Null for an `SMS_OTP` reset, where completing the reset enrolls the new number directly with no separate enrollment step. + example: PENDING + expiresAt: + type: string + format: date-time + description: When the reset window closes. Poll no longer than this. + example: '2025-10-03T12:15:00Z' + completedAt: + type: + - string + - 'null' + format: date-time + description: When the reset completed; null until then. + example: null + TwoFactorResetCompleteRequest: type: object - description: Request body for generating a hosted KYC link for an existing customer. + description: Optional body for completing a 2FA reset. Only needed when resetting the `SMS_OTP` factor to a new phone number; omit the body entirely otherwise. properties: - redirectUri: - type: string - format: uri - description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`. - example: https://app.example.com/onboarding/completed - KycProvider: - type: string - description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK. - enum: - - SUMSUB - example: SUMSUB - KycLinkResponse: + mobile: + type: object + description: For an `SMS_OTP` reset, the new mobile number to enroll as the customer completes the reset. Required for an `SMS_OTP` reset; ignored for `TOTP` / `PASSKEY` resets. + required: + - countryCode + - number + properties: + countryCode: + type: string + description: The country dialing code, including the leading `+`. + example: '+1' + number: + type: string + description: The national subscriber number, without the country code. + example: '4155550123' + ScaChallenge: type: object - description: A hosted KYC link that the customer can complete to verify their identity. + description: |- + A Strong Customer Authentication challenge that must be satisfied before a money-movement operation can complete. This object is **only present when the customer is in a region where SCA is required** (the EU); for customers outside SCA-regulated regions it is omitted entirely and no action is needed. + + When present on a quote, authorize it by submitting an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize`. + + **A single operation may require more than one authorization, in sequence.** Treat `scaChallenge` as *the challenge to satisfy now*, not "the only one". After each authorize, re-inspect the returned quote/transaction: if it is still `PENDING_AUTHORIZATION`, it carries the **next** `scaChallenge` (a new `id`) — authorize that too, and repeat until it leaves `PENDING_AUTHORIZATION`. Do not assume one authorization releases the transfer. The number of authorizations is flow-dependent and **may decrease in future**: for example, a cross-currency send today authorizes the currency conversion and the payout as two separate challenges; a future update may collapse them into one. A client written to loop on status handles any count unchanged. required: - - kycUrl + - id - expiresAt - - provider + - factor + - availableFactors properties: - kycUrl: + id: type: string - description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again. - example: https://kyc.lightspark.com/onboard/abc123def456 + description: Unique identifier for this challenge. The server resolves the active challenge from the quote or transaction being authorized, so this field need not be supplied back; it is informational (e.g. for logging or correlation). + example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007 expiresAt: type: string format: date-time - description: Time at which the hosted link expires and can no longer be used. - example: '2027-01-15T14:32:00Z' - provider: - $ref: '#/components/schemas/KycProvider' - token: - type: string - description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer. - example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ContactVerificationConfirmRequest: + description: Absolute UTC timestamp after which this challenge can no longer be authorized. + example: '2025-10-03T12:05:00Z' + factor: + $ref: '#/components/schemas/ScaFactor' + description: The factor this challenge was issued for. Defaults to `SMS_OTP`. + availableFactors: + type: array + description: The factors the customer may use to satisfy this challenge. + items: + $ref: '#/components/schemas/ScaFactor' + example: + - SMS_OTP + purpose: + type: + - string + - 'null' + description: Optional, informational label for what this particular challenge in the sequence authorizes — useful for step UX (e.g. "Authorize the currency conversion" vs "Authorize the payout"). Known values include `CURRENCY_CONVERSION`, `PAYOUT`, and `TRANSFER`, but the set is **non-exhaustive and may grow** — treat unrecognized values as a generic authorization step and do not branch program logic on it. Omitted when steps are not distinguished (e.g. a single-authorization flow). + example: PAYOUT + passkeyAssertionOptions: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion request options (including the relying-party id, challenge, and allowed credentials), present only when `factor` is `PASSKEY`. Pass to the device's WebAuthn API to produce the assertion submitted back in `ScaAuthorization.passkeyAssertion`. + passkeyAllowedOrigins: + type: + - array + - 'null' + items: + type: string + description: The origins the WebAuthn ceremony may run against. Populated for enrollment and login passkey challenges; the origin the assertion is produced against must be one of these and echoed back as `ScaAuthorization.origin`. Per-transaction passkey challenges omit this (they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin` for how to source the origin in that case. + example: + - https://app.example.com + BeneficiaryTrustStart: type: object - description: Request body for confirming an email or phone verification challenge. - required: - - code + description: The SCA challenge (if any) a caller authorizes to finish trusting (or untrusting) a beneficiary. The beneficiary is identified by its `externalAccountId`, so the confirm call needs no separate handle. `scaChallenge` is omitted when no challenge is issued; the caller then confirms without a `challengeId`. properties: + scaChallenge: + $ref: '#/components/schemas/ScaChallenge' + description: The SCA challenge to satisfy on the confirm call. Omitted when no challenge is issued. + BeneficiaryTrustConfirmRequest: + type: object + description: Confirms trusting or untrusting a beneficiary by submitting the SCA proof. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`) and, when the start issued one, the `challengeId`. The beneficiary is identified by the `externalAccountId` in the path — no separate handle is needed. + anyOf: + - required: + - code + - required: + - passkeyAssertion + - origin + properties: + challengeId: + type: + - string + - 'null' + description: The challenge handle from the trust start's `scaChallenge`, when one was issued. Omit when the start returned no challenge. code: - type: string - description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`. + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`. example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the challenge's assertion options. Required when satisfying a `PASSKEY` challenge. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against. Required alongside `passkeyAssertion`; omit it for the `code` path. + example: https://app.example.com + BeneficiaryTrustConfirm: + type: object + description: The result of a confirm-trust / confirm-untrust call. `trusted` is `true` after a successful trust and `false` after a successful untrust. + required: + - trusted + properties: + trusted: + type: boolean + description: Whether the beneficiary is now trusted. `true` after a successful trust, `false` after a successful untrust. InternalAccountType: title: Internal Account Type type: string @@ -11007,19 +13969,6 @@ components: - `FROZEN`: The account cannot send or receive payments. Grid may freeze an account in response to compliance or fraud signals; payments are blocked while the account remains frozen. - `FAILED`: The account could not be provisioned. Grid was unable to create the underlying account, so it cannot send or receive payments and requires remediation. example: ACTIVE - CurrencyAmount: - type: object - required: - - amount - - currency - properties: - amount: - type: integer - format: int64 - description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC) - example: 12550 - currency: - $ref: '#/components/schemas/Currency' PaymentAccountType: type: string enum: @@ -11045,10 +13994,12 @@ components: - LIGHTNING - SOLANA_WALLET - TRON_WALLET + - PLASMA_WALLET - POLYGON_WALLET - BASE_WALLET - ETHEREUM_WALLET - EMBEDDED_WALLET + - BITCOIN_L1 description: Type of payment account or wallet example: USD_ACCOUNT BasePaymentAccountInfo: @@ -11081,10 +14032,43 @@ components: minLength: 9 maxLength: 9 pattern: ^[0-9]{9}$ + bankName: + type: string + description: The name of the financial institution holding the account. Optional on every rail, and recommended for wires, where it identifies the beneficiary's institution on the payment message. + example: Chase Bank + minLength: 1 + maxLength: 140 + bankAccountType: + type: string + description: Whether the account is a checking or a savings account. Optional on every rail; when omitted, the account is treated as a checking account. + enum: + - CHECKING + - SAVINGS + example: CHECKING + intermediaryBankName: + type: string + description: The name of the intermediary financial institution, for accounts reachable only through a correspondent bank. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: JPMorgan Chase Bank + minLength: 1 + maxLength: 140 + intermediaryRoutingNumber: + type: string + description: The ABA routing number of the intermediary financial institution. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: '021000021' + minLength: 9 + maxLength: 9 + pattern: ^[0-9]{9}$ + fiToFiInformation: + type: string + description: Bank-to-bank instructions carried alongside the payment. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW. + example: /BNF/Invoice 4471 + maxLength: 210 example: accountType: USD_ACCOUNT accountNumber: '1234567890' routingNumber: '021000021' + bankName: Chase Bank + bankAccountType: CHECKING UsdAccountInfo: allOf: - $ref: '#/components/schemas/UsdAccountInfoBase' @@ -12057,6 +15041,32 @@ components: description: Type of asset enum: - USDT + PlasmaWalletInfo: + type: object + required: + - address + - accountType + properties: + accountType: + type: string + enum: + - PLASMA_WALLET + address: + type: string + description: Plasma wallet address + example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + PaymentPlasmaWalletInfo: + title: Plasma Wallet + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletInfo' + - type: object + properties: + assetType: + type: string + description: Type of asset + enum: + - USDT PolygonWalletInfo: type: object required: @@ -12135,6 +15145,29 @@ components: description: Type of asset enum: - USDC + - USDT + PaymentBitcoinDepositAddressInfo: + title: Bitcoin L1 Deposit Address + allOf: + - $ref: '#/components/schemas/BasePaymentAccountInfo' + - type: object + required: + - address + properties: + accountType: + type: string + enum: + - BITCOIN_L1 + address: + type: string + description: On-chain Bitcoin (L1) deposit address to send funds to + example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq + network: + type: string + description: The blockchain network for the deposit address. + enum: + - BITCOIN + example: BITCOIN AedAccountInfoBase: type: object required: @@ -13512,9 +16545,11 @@ components: - $ref: '#/components/schemas/PaymentLightningInvoiceInfo' - $ref: '#/components/schemas/PaymentSolanaWalletInfo' - $ref: '#/components/schemas/PaymentTronWalletInfo' + - $ref: '#/components/schemas/PaymentPlasmaWalletInfo' - $ref: '#/components/schemas/PaymentPolygonWalletInfo' - $ref: '#/components/schemas/PaymentBaseWalletInfo' - $ref: '#/components/schemas/PaymentEthereumWalletInfo' + - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo' - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo' discriminator: propertyName: accountType @@ -13539,9 +16574,11 @@ components: LIGHTNING: '#/components/schemas/PaymentLightningInvoiceInfo' SOLANA_WALLET: '#/components/schemas/PaymentSolanaWalletInfo' TRON_WALLET: '#/components/schemas/PaymentTronWalletInfo' + PLASMA_WALLET: '#/components/schemas/PaymentPlasmaWalletInfo' POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo' BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo' ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo' + BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo' AED_ACCOUNT: '#/components/schemas/PaymentAedAccountInfo' KES_ACCOUNT: '#/components/schemas/PaymentKesAccountInfo' MWK_ACCOUNT: '#/components/schemas/PaymentMwkAccountInfo' @@ -13723,6 +16760,7 @@ components: - BASE_WALLET - ETHEREUM_WALLET - LIGHTNING + - PLASMA_WALLET - POLYGON_WALLET - SOLANA_WALLET - SPARK_WALLET @@ -15828,6 +18866,11 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletInfo' + PlasmaWalletExternalAccountInfo: + title: Plasma Wallet + allOf: + - $ref: '#/components/schemas/BaseExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletInfo' SolanaWalletExternalAccountInfo: title: Solana Wallet allOf: @@ -15937,6 +18980,7 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -15986,6 +19030,7 @@ components: LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' LIGHTNING_ACCOUNT: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' + PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -16855,6 +19900,7 @@ components: - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo' - $ref: '#/components/schemas/LightningExternalAccountInfo' - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo' + - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo' - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletExternalAccountInfo' - $ref: '#/components/schemas/TronWalletExternalAccountInfo' @@ -16903,6 +19949,7 @@ components: ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo' LIGHTNING: '#/components/schemas/LightningExternalAccountInfo' POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo' + PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo' SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo' SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo' TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo' @@ -17395,6 +20442,7 @@ components: enum: - CREATED - PENDING + - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - REJECTED @@ -17408,6 +20456,7 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -17447,6 +20496,7 @@ components: - BASE - POLYGON - TRON + - PLASMA - SPARK description: The blockchain network an on-chain transaction settled on. Whether this is the mainnet or a test network (e.g. Solana devnet) is determined by your platform's environment — sandbox platforms operate on test networks, production platforms on mainnet — mirroring how `cryptoNetwork` is interpreted elsewhere in the API. example: SOLANA @@ -17511,6 +20561,43 @@ components: mapping: ACCOUNT: '#/components/schemas/AccountTransactionDestination' UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionDestination' + Refund: + type: object + required: + - reference + - initiatedAt + - status + properties: + reference: + type: string + description: The unique reference ID of the refund + example: UMA-Q12345-REFUND + initiatedAt: + type: string + format: date-time + description: When the refund was initiated + example: '2025-08-15T14:30:00Z' + settledAt: + type: string + format: date-time + description: When the refund was settled + example: '2025-08-15T14:35:00Z' + status: + type: string + enum: + - PENDING + - COMPLETED + - FAILED + description: Current status of the refund + example: COMPLETED + reason: + type: string + enum: + - TRANSACTION_FAILED + - USER_CANCELLATION + - TIMEOUT + description: Reason for the refund + example: TRANSACTION_FAILED CounterpartyInformation: type: object description: Additional information about the counterparty, if available and relevant to the transaction and platform. @@ -17545,11 +20632,11 @@ components: $ref: '#/components/schemas/TransactionDestinationOneOf' customerId: type: string - description: System ID of the customer (sender for outgoing, recipient for incoming) + description: System ID of the customer this transaction belongs to example: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: type: string - description: Platform-specific ID of the customer (sender for outgoing, recipient for incoming) + description: Platform-specific ID of the customer this transaction belongs to example: 18d3e5f7b4a9c2 settledAt: type: string @@ -17579,6 +20666,21 @@ components: type: string description: Optional memo or description for the payment example: 'Payment for invoice #1234' + sentAmount: + $ref: '#/components/schemas/CurrencyAmount' + description: Amount sent in the sender's currency + exchangeRate: + type: number + description: Number of sending currency units per receiving currency unit. + exclusiveMinimum: 0 + example: 1.08 + quoteId: + type: string + description: The ID of the quote that was used to trigger this payment + example: Quote:019542f5-b3e7-1d02-0000-000000000006 + refund: + $ref: '#/components/schemas/Refund' + description: The refund if transaction was refunded. counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' TransactionSourceType: @@ -17694,65 +20796,32 @@ components: example: '021000020123456' onChainTransaction: $ref: '#/components/schemas/OnChainTransaction' - description: On-chain transaction that delivered the funding, when the funds arrived from an external crypto wallet. Populated once the crypto transfer has settled. - description: Transaction was funded using an external funding source. All originator fields are optional and populated on a best-effort basis depending on what the funding source provides. - TransactionSourceOneOf: - oneOf: - - $ref: '#/components/schemas/AccountTransactionSource' - - $ref: '#/components/schemas/UmaAddressTransactionSource' - - $ref: '#/components/schemas/RealtimeFundingTransactionSource' - discriminator: - propertyName: sourceType - mapping: - ACCOUNT: '#/components/schemas/AccountTransactionSource' - UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionSource' - REALTIME_FUNDING: '#/components/schemas/RealtimeFundingTransactionSource' - ReconciliationInstructions: - type: object - minProperties: 1 - description: Instructions for reconciling a payment with this transaction. For the on-chain transaction to or from an external crypto wallet that is the transaction's own source or destination, use the `onChainTransaction` on the relevant source or destination instead. - properties: - reference: - type: string - description: Unique reference code to include with the payment to match it with the correct incoming transaction, when available. - example: UMA-Q12345-REF - transactionHash: - type: string - description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. - example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' - IncomingRateDetails: - description: 'Details about the rate and fees for an incoming transaction. Note: `gridApiFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' - type: object - required: - - gridApiMultiplier - - gridApiFixedFee - - gridApiVariableFeeRate - - gridApiVariableFeeAmount - properties: - gridApiMultiplier: - type: number - format: double - description: The underlying multiplier from the mSATS to the receiving currency, including variable fees. - exclusiveMinimum: 0 - example: 0.925 - gridApiFixedFee: - type: integer - format: int64 - description: The fixed fee charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). - minimum: 0 - example: 10 - gridApiVariableFeeRate: - type: number - format: double - description: The variable fee rate charged by the Grid product to execute the quote as a percentage of the receiving currency amount. - minimum: 0 - example: 0.003 - gridApiVariableFeeAmount: - type: number - format: int64 - description: The variable fee amount charged by the Grid product to execute the quote in the smallest unit of the receiving currency (eg. cents). This is the receiving amount times gridApiVariableFeeRate. - minimum: 0 - example: 30 + description: On-chain transaction that delivered the funding, when the funds arrived from an external crypto wallet. Populated once the crypto transfer has settled. + description: Transaction was funded using an external funding source. All originator fields are optional and populated on a best-effort basis depending on what the funding source provides. + TransactionSourceOneOf: + oneOf: + - $ref: '#/components/schemas/AccountTransactionSource' + - $ref: '#/components/schemas/UmaAddressTransactionSource' + - $ref: '#/components/schemas/RealtimeFundingTransactionSource' + discriminator: + propertyName: sourceType + mapping: + ACCOUNT: '#/components/schemas/AccountTransactionSource' + UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionSource' + REALTIME_FUNDING: '#/components/schemas/RealtimeFundingTransactionSource' + ReconciliationInstructions: + type: object + minProperties: 1 + description: Instructions for reconciling a payment with this transaction. For the on-chain transaction to or from an external crypto wallet that is the transaction's own source or destination, use the `onChainTransaction` on the relevant source or destination instead. + properties: + reference: + type: string + description: Unique reference code to include with the payment to match it with the correct incoming transaction, when available. + example: UMA-Q12345-REF + transactionHash: + type: string + description: Transaction hash of the internal settlement transfer used to deliver a UMA payment — the inter-VASP settlement leg (e.g. USDC on Solana to the receiving partner), when available. This is not a transfer to a customer's own wallet; for that, see the `onChainTransaction` on the transaction's source or destination. + example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566' IncomingTransactionFailureReason: type: string enum: @@ -17786,15 +20855,12 @@ components: fees: type: integer format: int64 - description: The total fees available from the receive quote in the smallest unit of the receiving currency (eg. cents). + description: The total fees available from the receive quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Included for all transactions except those with "CREATED" status - rateDetails: - $ref: '#/components/schemas/IncomingRateDetails' - description: Details about the rate and fees for the transaction. failureReason: $ref: '#/components/schemas/IncomingTransactionFailureReason' description: If the transaction failed, this field provides the reason for failure. @@ -17802,6 +20868,7 @@ components: type: string enum: - PENDING + - PENDING_AUTHORIZATION - EXPIRED - PROCESSING - COMPLETED @@ -17812,47 +20879,11 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | | `FAILED` | Something went wrong — accompanied by a `failureReason` | - Refund: - type: object - required: - - reference - - initiatedAt - - status - properties: - reference: - type: string - description: The unique reference ID of the refund - example: UMA-Q12345-REFUND - initiatedAt: - type: string - format: date-time - description: When the refund was initiated - example: '2025-08-15T14:30:00Z' - settledAt: - type: string - format: date-time - description: When the refund was settled - example: '2025-08-15T14:35:00Z' - status: - type: string - enum: - - PENDING - - COMPLETED - - FAILED - description: Current status of the refund - example: COMPLETED - reason: - type: string - enum: - - TRANSACTION_FAILED - - USER_CANCELLATION - - TIMEOUT - description: Reason for the refund - example: TRANSACTION_FAILED OutgoingRateDetails: description: 'Details about the rate and fees for an outgoing transaction or quote. Note: `counterpartyFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.' type: object @@ -17908,7 +20939,15 @@ components: - LIGHTNING_PAYMENT_FAILED - FUNDING_AMOUNT_MISMATCH - COUNTERPARTY_POST_TX_FAILED - description: Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. + - SCA_NOT_COMPLETED + - EXECUTION_FAILED_POST_DEBIT + - SETTLEMENT_FAILED + - TIMEOUT + - MANUAL_REFUND + description: |- + Reason for failure of an outgoing transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. + + `SCA_NOT_COMPLETED` means the customer did not satisfy the Strong Customer Authentication challenge before it expired, so the transaction was never authorized and no funds were moved. Only occurs for customers in a region where SCA is required (e.g. the EU). Create a new quote to try again, and have the customer authorize it while the challenge is live. RailSelectionMode: type: string enum: @@ -17940,24 +20979,21 @@ components: receivedAmount: $ref: '#/components/schemas/CurrencyAmount' description: Amount to be received by recipient in the recipient's currency - exchangeRate: - type: number - description: Number of sending currency units per receiving currency unit. - exclusiveMinimum: 0 - example: 1.08 fees: type: integer format: int64 description: The fees associated with the quote in the smallest unit of the sending currency (eg. cents). minimum: 0 example: 10 + platformFees: + type: integer + format: int64 + description: The portion of `fees` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `fees`. + minimum: 0 + example: 5 reconciliationInstructions: $ref: '#/components/schemas/ReconciliationInstructions' description: Reconciliation details for this transaction. For the on-chain hash of a crypto payout to an external wallet, see the destination's `onChainTransaction` instead. - quoteId: - type: string - description: The ID of the quote that was used to trigger this payment - example: Quote:019542f5-b3e7-1d02-0000-000000000006 paymentInstructions: type: array description: Payment instructions for executing the payment. @@ -17979,9 +21015,6 @@ components: assetType: BTC address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - refund: - $ref: '#/components/schemas/Refund' - description: The refund if transaction was refunded. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. @@ -18208,6 +21241,24 @@ components: description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail. allOf: - $ref: '#/components/schemas/PaymentRail' + PurposeOfPayment: + type: string + description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). + enum: + - GIFT + - SELF + - GOODS_OR_SERVICES + - EDUCATION + - HEALTH_OR_MEDICAL + - REAL_ESTATE_PURCHASE + - TAX_PAYMENT + - LOAN_PAYMENT + - UTILITY_BILL + - DONATION + - TRAVEL + - FAMILY_SUPPORT + - SALARY_PAYMENT + - OTHER TransferOutRequest: type: object required: @@ -18230,6 +21281,8 @@ components: maxLength: 80 description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' example: '12345' + purposeOfPayment: + $ref: '#/components/schemas/PurposeOfPayment' CurrencyPreference: type: object required: @@ -18489,11 +21542,12 @@ components: type: string enum: - PENDING + - PENDING_AUTHORIZATION - PROCESSING - COMPLETED - FAILED - EXPIRED - description: Current status of the quote + description: 'Current status of the quote. `PENDING_AUTHORIZATION` occurs only for customers in a region where Strong Customer Authentication is required (e.g. EU): the quote carries an `scaChallenge` that must be authorized before execution, and for realtime-funding sources `paymentInstructions` are withheld until it is satisfied.' example: PENDING createdAt: type: string @@ -18537,6 +21591,12 @@ components: description: 'The fees associated with the quote in the smallest unit of the sending currency (eg. cents). Note: this value may fluctuate between quotes — some underlying fee components are defined in the receiving currency, so their equivalent in the sending currency moves with the FX rate. The fees shown here are locked only for the lifetime of this quote.' minimum: 0 example: 10 + platformFeesIncluded: + type: integer + format: int64 + description: The portion of `feesIncluded` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `feesIncluded`. May be omitted from payloads produced before platform fees existed. + minimum: 0 + example: 5 paymentInstructions: type: array description: Payment instructions for executing the payment. This is not required when using an internal account source. @@ -18562,41 +21622,22 @@ components: type: string description: The ID of the transaction created from this quote. example: Transaction:019542f5-b3e7-1d02-0000-000000000005 - remittanceInformation: - type: string - maxLength: 80 - description: 'Free-form information about the payment that travels with it to the recipient, as provided on the quote request. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.' - example: '12345' counterpartyInformation: $ref: '#/components/schemas/CounterpartyInformation' description: Additional information about the counterparty, if available and required by the platform in their configuration. rateDetails: $ref: '#/components/schemas/OutgoingRateDetails' description: Details about the rate and fees for the transaction. + scaChallenge: + $ref: '#/components/schemas/ScaChallenge' + readOnly: true + description: 'Present only while `status` is `PENDING_AUTHORIZATION`: the Strong Customer Authentication challenge to satisfy before this quote can be executed (or, for realtime-funding sources, before `paymentInstructions` are issued). Omitted for customers outside SCA-regulated regions (non-EU).' QuoteLockSide: type: string enum: - SENDING - RECEIVING description: The side of the quote which should be locked and specified in the `lockedCurrencyAmount`. For example, if I want to send exactly $5 MXN from my wallet, I would set this to "sending", and the `lockedCurrencyAmount` to 500 (in cents). If I want the receiver to receive exactly $10 USD, I would set this to "receiving" and the `lockedCurrencyAmount` to 10000 (in cents). - PurposeOfPayment: - type: string - description: The purpose of the payment. This may be required when sending to certain geographies (e.g. India). - enum: - - GIFT - - SELF - - GOODS_OR_SERVICES - - EDUCATION - - HEALTH_OR_MEDICAL - - REAL_ESTATE_PURCHASE - - TAX_PAYMENT - - LOAN_PAYMENT - - UTILITY_BILL - - DONATION - - TRAVEL - - FAMILY_SUPPORT - - SALARY_PAYMENT - - OTHER QuoteRequest: type: object required: @@ -18642,6 +21683,9 @@ components: example: '12345' purposeOfPayment: $ref: '#/components/schemas/PurposeOfPayment' + scaFactor: + $ref: '#/components/schemas/ScaFactor' + description: Optional preferred factor for a Strong Customer Authentication challenge issued at quote creation. Only relevant for a realtime-funding source in a region where SCA is required (e.g. EU); ignored otherwise. Valid values are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected. When the quote is returned in `PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`. senderCustomerInfo: type: object additionalProperties: true @@ -18651,6 +21695,41 @@ components: example: FULL_NAME: Jane Receiver NATIONALITY: FR + ExecuteQuoteRequest: + type: object + description: Optional body for executing a quote. Only needed to request a specific Strong Customer Authentication factor (`scaFactor`) for the challenge this call issues; omit the body entirely otherwise. + properties: + scaFactor: + $ref: '#/components/schemas/ScaFactor' + description: Optional preferred factor for the Strong Customer Authentication challenge this call issues. Only relevant for customers in a region where SCA is required (e.g. EU); ignored otherwise. Valid values for a per-transaction challenge are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected here. Omit to default to `SMS_OTP`. + ScaAuthorization: + type: object + description: Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for `SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a `passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound to the origin it was produced against, and a passkey confirmation is rejected without it. + properties: + code: + type: + - string + - 'null' + description: The one-time code the customer received by SMS, or read from their authenticator app. In sandbox, the code is always `123456`. + example: '123456' + passkeyAssertion: + type: + - object + - 'null' + additionalProperties: true + description: Opaque WebAuthn assertion produced by the device from the challenge's `passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge. + origin: + type: + - string + - 'null' + description: The WebAuthn origin the `passkeyAssertion` was produced against. **Required** alongside `passkeyAssertion`; omit it for the `code` path. When the challenge lists `passkeyAllowedOrigins` (enrollment / login challenges), it must be one of those. A per-transaction passkey challenge carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in that case supply the origin your app invoked the WebAuthn API from, which must match the relying party in `passkeyAssertionOptions`. + example: https://app.example.com + endUserIpAddress: + type: + - string + - 'null' + description: 'The IP address of the end user''s device authorizing this operation, forwarded to the SCA provider where it feeds risk assessment and any transaction-risk exemption. Supply the customer''s address, not your server''s. Optional: the provider records it for money-movement authorizations, and ignores it for beneficiary trust changes.' + example: 203.0.113.42 TransactionListResponse: type: object required: @@ -18693,6 +21772,13 @@ components: type: string description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes. example: RESTRICTED_JURISDICTION + CancelTransactionRequest: + type: object + properties: + reason: + type: string + description: Optional reason for cancelling the transaction. This is just for debugging purposes or can be used for a platform's own purposes. + example: REQUESTED_AFTER_HOURS EstimateCryptoWithdrawalFeeRequest: type: object required: @@ -19623,33 +22709,6 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the `encryptedSessionSigningKey` must not be used to sign further requests. example: '2026-04-09T15:30:01Z' - Error429: - type: object - required: - - message - - status - - code - properties: - status: - type: integer - enum: - - 429 - description: HTTP status code - code: - type: string - description: | - | Error Code | Description | - |------------|-------------| - | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header | - enum: - - RATE_LIMITED - message: - type: string - description: Error message - details: - type: object - description: Additional error details - additionalProperties: true AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. @@ -20579,7 +23638,7 @@ components: - amount - currency - merchant - description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the endpoint documentation for the suffix table. + description: 'Sandbox-only request body shared by the card authorization-family simulate endpoints: `simulate/authorization`, `simulate/credit_authorization`, `simulate/financial_authorization`, `simulate/financial_credit_authorization`, and `simulate/credit_authorization_advice`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` documentation for the suffix table.' properties: amount: type: integer @@ -20591,6 +23650,16 @@ components: $ref: '#/components/schemas/Currency' merchant: $ref: '#/components/schemas/CardMerchant' + SandboxCardSimulationResponse: + type: object + required: + - issuerTransactionToken + description: Response body for the sandbox card-event simulators. The simulate call pokes the card issuer's sandbox; the resulting card operation is delivered asynchronously via the issuer's events webhook, never synchronously in this response. + properties: + issuerTransactionToken: + type: string + description: The card issuer's transaction token for the simulated event. Correlates the eventual webhook-delivered card operation back to this simulate call. + example: f3a1c2d4-5b6e-7890-abcd-ef0123456789 SandboxCardClearingRequest: type: object required: @@ -20625,6 +23694,41 @@ components: description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded). exclusiveMinimum: 0 example: 1500 + SandboxCardBalanceInquiryRequest: + type: object + required: + - merchant + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/balance_inquiry`. Drives a balance-inquiry authorization against the card. A balance inquiry is always a `0`-amount authorization, so it carries no `amount`. + properties: + merchant: + $ref: '#/components/schemas/CardMerchant' + SandboxCardAuthorizationAdviceRequest: + type: object + required: + - cardTransactionId + - amount + description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization_advice`. Drives an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` the advice re-sizes. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 + amount: + type: integer + format: int64 + description: The new *total* authorized amount, in the smallest unit of the transaction's currency, that the advice re-sizes the authorization to. + exclusiveMinimum: 0 + example: 2000 + SandboxCardTransactionRefRequest: + type: object + required: + - cardTransactionId + description: Sandbox-only request body for simulate endpoints keyed only by an existing card transaction — currently `POST /sandbox/cards/{id}/simulate/return_reversal`. + properties: + cardTransactionId: + type: string + description: The id of the `CardTransaction` to act against. + example: CardTransaction:019542f5-b3e7-1d02-0000-000000000100 StablecoinIssuanceStatus: type: string enum: @@ -20904,67 +24008,16 @@ components: SAME_DAY_ACH_DEBIT: '#/components/schemas/StablecoinSameDayAchDebitFundingSource' GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalFundingSource' PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceFundingSource' - StablecoinMintDestinationBase: + StablecoinMintDestination: type: object + description: Account that receives the minted stablecoin. External and Grid-managed accounts are represented as a single account reference, matching the `accountId` pattern used elsewhere in the API; the id prefix (`ExternalAccount:` or `InternalAccount:`) determines which. In the initial Brale-backed flow this must reference an active Spark external account. required: - - type + - accountId properties: - type: + accountId: type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - example: EXTERNAL_ACCOUNT - StablecoinExternalAccountMintDestination: - title: External Account - allOf: - - $ref: '#/components/schemas/StablecoinMintDestinationBase' - - type: object - required: - - type - - externalAccountId - properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - description: External account mint destination. Must reference an active Spark external account. - example: EXTERNAL_ACCOUNT - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - description: External account mint destination. - StablecoinGridInternalMintDestination: - title: Grid Internal Account - allOf: - - $ref: '#/components/schemas/StablecoinMintDestinationBase' - - type: object - required: - - type - - accountId - properties: - type: - type: string - enum: - - GRID_INTERNAL_ACCOUNT - description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. - example: GRID_INTERNAL_ACCOUNT - accountId: - type: string - description: Grid internal receiving account id. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 - description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. - StablecoinMintDestination: - oneOf: - - $ref: '#/components/schemas/StablecoinExternalAccountMintDestination' - - $ref: '#/components/schemas/StablecoinGridInternalMintDestination' - discriminator: - propertyName: type - mapping: - EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountMintDestination' - GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalMintDestination' + description: Grid account receiving the minted stablecoin. Accepts an `ExternalAccount:` id (initial Brale-backed flow) or an `InternalAccount:` id for Grid-managed destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 StablecoinMintRequest: type: object required: @@ -21064,24 +24117,14 @@ components: valueType: ACME StablecoinOperationDestination: type: object - description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint and burn destination request variants) so the field is unambiguously deserializable regardless of operation type: `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' + description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged (flat) shape so the field is unambiguously deserializable regardless of operation type: `accountId` identifies the receiving account (its `ExternalAccount:` / `InternalAccount:` prefix disambiguates external vs Grid-managed), and `rail` is present for burn (fiat payout) destinations and absent for mint destinations.' required: - - type + - accountId properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Destination variant. - externalAccountId: - type: string - description: Grid `ExternalAccount` id, when the destination is an external account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 accountId: type: string - description: Grid internal account id, when the destination is a Grid-managed account. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: Grid account that received the funds. An `ExternalAccount:` id for external destinations or an `InternalAccount:` id for Grid-managed destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 rail: type: string enum: @@ -21462,6 +24505,9 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED + - INCOMING_PAYMENT.REFUND_PENDING + - INCOMING_PAYMENT.REFUND_COMPLETED + - INCOMING_PAYMENT.REFUND_FAILED - CUSTOMER.KYC_APPROVED - CUSTOMER.KYC_REJECTED - CUSTOMER.KYC_PENDING @@ -21482,6 +24528,11 @@ components: - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - CARD.FUNDING_SOURCE_CHANGE + - CARD_TRANSACTION.AUTHORIZED + - CARD_TRANSACTION.PARTIALLY_SETTLED + - CARD_TRANSACTION.SETTLED + - CARD_TRANSACTION.REFUNDED + - CARD_TRANSACTION.EXCEPTION - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -21542,6 +24593,9 @@ components: - INCOMING_PAYMENT.PENDING - INCOMING_PAYMENT.COMPLETED - INCOMING_PAYMENT.FAILED + - INCOMING_PAYMENT.REFUND_PENDING + - INCOMING_PAYMENT.REFUND_COMPLETED + - INCOMING_PAYMENT.REFUND_FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -21584,6 +24638,7 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING + - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED @@ -21702,6 +24757,23 @@ components: type: string enum: - CARD.FUNDING_SOURCE_CHANGE + CardTransactionWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/CardTransaction' + type: + type: string + enum: + - CARD_TRANSACTION.AUTHORIZED + - CARD_TRANSACTION.PARTIALLY_SETTLED + - CARD_TRANSACTION.SETTLED + - CARD_TRANSACTION.REFUNDED + - CARD_TRANSACTION.EXCEPTION requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml deleted file mode 100644 index 1dca3216f..000000000 --- a/openapi/components/schemas/stablecoins/StablecoinExternalAccountMintDestination.yaml +++ /dev/null @@ -1,19 +0,0 @@ -title: External Account -allOf: - - $ref: ./StablecoinMintDestinationBase.yaml - - type: object - required: - - type - - externalAccountId - properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - description: External account mint destination. Must reference an active Spark external account. - example: EXTERNAL_ACCOUNT - externalAccountId: - type: string - description: Grid `ExternalAccount` receiving the minted stablecoin. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 - description: External account mint destination. diff --git a/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml deleted file mode 100644 index 9da969cc8..000000000 --- a/openapi/components/schemas/stablecoins/StablecoinGridInternalMintDestination.yaml +++ /dev/null @@ -1,19 +0,0 @@ -title: Grid Internal Account -allOf: - - $ref: ./StablecoinMintDestinationBase.yaml - - type: object - required: - - type - - accountId - properties: - type: - type: string - enum: - - GRID_INTERNAL_ACCOUNT - description: Grid internal account mint destination. Reserved for future Grid-managed mint destinations. - example: GRID_INTERNAL_ACCOUNT - accountId: - type: string - description: Grid internal receiving account id. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 - description: Grid internal account mint destination, reserved for future Grid-managed mint destinations. diff --git a/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml index debfa29a9..970a69891 100644 --- a/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinMintDestination.yaml @@ -1,8 +1,18 @@ -oneOf: - - $ref: ./StablecoinExternalAccountMintDestination.yaml - - $ref: ./StablecoinGridInternalMintDestination.yaml -discriminator: - propertyName: type - mapping: - EXTERNAL_ACCOUNT: ./StablecoinExternalAccountMintDestination.yaml - GRID_INTERNAL_ACCOUNT: ./StablecoinGridInternalMintDestination.yaml +type: object +description: >- + Account that receives the minted stablecoin. External and Grid-managed + accounts are represented as a single account reference, matching the + `accountId` pattern used elsewhere in the API; the id prefix + (`ExternalAccount:` or `InternalAccount:`) determines which. In the + initial Brale-backed flow this must reference an active Spark external + account. +required: + - accountId +properties: + accountId: + type: string + description: >- + Grid account receiving the minted stablecoin. Accepts an + `ExternalAccount:` id (initial Brale-backed flow) or an + `InternalAccount:` id for Grid-managed destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201 diff --git a/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml b/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml deleted file mode 100644 index bf0257071..000000000 --- a/openapi/components/schemas/stablecoins/StablecoinMintDestinationBase.yaml +++ /dev/null @@ -1,11 +0,0 @@ -type: object -required: - - type -properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Mint destination variant. In the initial Brale-backed flow, `EXTERNAL_ACCOUNT` must reference an active Spark external account. - example: EXTERNAL_ACCOUNT diff --git a/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml b/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml index 908bc02ec..c6fe86c64 100644 --- a/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml +++ b/openapi/components/schemas/stablecoins/StablecoinOperationDestination.yaml @@ -1,28 +1,22 @@ type: object description: >- Destination of a stablecoin operation, as reported on a - `StablecoinOperation`. A single merged shape (superset of the - mint and burn destination request variants) so the field is - unambiguously deserializable regardless of operation type: `rail` - is present for burn (fiat payout) destinations and absent for mint - destinations. + `StablecoinOperation`. A single merged (flat) shape so the field is + unambiguously deserializable regardless of operation type: `accountId` + identifies the receiving account (its `ExternalAccount:` / + `InternalAccount:` prefix disambiguates external vs Grid-managed), and + `rail` is present for burn (fiat payout) destinations and absent for + mint destinations. required: - - type + - accountId properties: - type: - type: string - enum: - - EXTERNAL_ACCOUNT - - GRID_INTERNAL_ACCOUNT - description: Destination variant. - externalAccountId: - type: string - description: Grid `ExternalAccount` id, when the destination is an external account. - example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 accountId: type: string - description: Grid internal account id, when the destination is a Grid-managed account. - example: InternalAccount:019542f5-b3e7-1d02-0000-000000000202 + description: >- + Grid account that received the funds. An `ExternalAccount:` id for + external destinations or an `InternalAccount:` id for Grid-managed + destinations. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205 rail: type: string enum: