diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4056299bb..d7c4a38f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -46,8 +46,5 @@ jobs: - name: Install dependencies run: npm ci - - name: Install mint - run: npm i -g mint - - name: Lint run: make lint diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index ed1ad3019..93887e1aa 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -7482,7 +7482,7 @@ paths: 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. + 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. A provisioned `PENDING_AUTH` card using this funding source becomes `ACTIVE` automatically. Cards still provisioning remain `PROCESSING`; frozen and closed cards retain their 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. @@ -7669,7 +7669,7 @@ paths: 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. + Revoke an `ACTIVE` delegated signing key. An active card using this funding source becomes `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"`. Frozen and closed cards retain their status. 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 @@ -9125,7 +9125,7 @@ paths: A platform may be limited to a maximum number of live cards. Once that limit is reached, further issuance is rejected with `CARD_LIMIT_REACHED` until a card is closed or Lightspark raises the limit. Cards in `CLOSED` status do not count toward the limit. - New cards start in `status: "PROCESSING"` while the card issuer provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). + New cards start in `status: "PROCESSING"` while the card issuer provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). A provisioned card funded by a USDB Embedded Wallet remains `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"` until all signed retries of `POST /auth/delegated-keys` are complete. Replacing USD funding with USDB without an active key, or revoking the required key, also moves an active card to `PENDING_AUTH`. Completing delegation or switching back to USD restores `ACTIVE`; frozen and closed cards keep their status. operationId: createCard tags: - Cards @@ -9337,8 +9337,8 @@ paths: description: | Update a card's `status`, bound `fundingSource`, and / or `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`, or set a new `threeDSecurePassword`. At least one field must be supplied. - - `status` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSource`, when supplied, replaces the card's bound internal account. It must belong to the customer and be denominated in the card's currency. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate requests. On card programs where Grid makes the authorization decision, the combination remains valid for `status` changes other than `CLOSED`. + - `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Activating a card whose USDB funding source lacks a usable delegated key returns `409 INVALID_STATE_TRANSITION`. `PENDING_AUTH` is managed by Grid and cannot be requested. Completing delegation does not unfreeze or reopen a card. + - `fundingSource`, when supplied, replaces the card's bound internal account. It must belong to the customer and use a currency supported by the card program, including USDB for USD cards. An active card becomes `PENDING_AUTH` when switched to USDB without an active delegated key. Switching back to USD restores readiness. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate requests. On card programs where Grid makes the authorization decision, the combination remains valid for `status` changes other than `CLOSED`. - `maxSpendPerTransaction` sets the largest amount the card can authorize on a single transaction. An authorization for exactly the limit is allowed, and a later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. Send a positive integer in the smallest unit of the card's currency (cents for USD) to set the limit, or null to remove it. If your platform config sets `cardConfigs.maxSpendPerTransaction`, the lower of the two applies. You can only send this when the card's `cardCapabilities.supportsSpendLimits` is true, and not together with `status: CLOSED`. - `maxSpendPerDay`, when supplied, replaces the card-specific cap on cumulative new spend during one UTC calendar day. Supply a positive integer in the smallest unit of the card's currency to set it or null to clear it. If the platform config sets `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. The card's `cardCapabilities.supportsSpendLimits` must be true. `maxSpendPerDay` cannot be supplied alongside `status: CLOSED`. - `maxTransactionsPerDay`, when supplied, replaces the card-specific cap on the number of transactions the card may authorize during one UTC calendar day. Supply a positive integer to set it or null to clear it. If the platform config sets `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. `maxTransactionsPerDay` requires the card's `cardCapabilities.supportsTransactionCountLimit` to be true and cannot be supplied alongside `status: CLOSED`. @@ -9431,7 +9431,7 @@ paths: schema: $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` 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 `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` transition is not one of `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, or `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`, or when activation is requested before delegated signing authorization is complete; with `CARD_ALREADY_CLOSED` when `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: @@ -13160,7 +13160,7 @@ webhooks: post: summary: Card status change description: | - Webhook that is called when a card's lifecycle status changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. + Webhook that is called when a card's lifecycle status changes. Fires on `PROCESSING → ACTIVE | PENDING_AUTH`, `ACTIVE ⇄ PENDING_AUTH` as funding sources or delegated signing authorization change, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → FROZEN`, and `→ CLOSED` transition. This endpoint should be implemented by clients of the Grid API. @@ -13211,6 +13211,32 @@ webhooks: issuerRef: lead_card_7a1b9c3d createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:11:00Z' + delegationRequired: + summary: Provisioned card requires funding authorization + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATUS_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-001 + status: PENDING_AUTH + statusReason: DELEGATION_REQUIRED + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + maxSpendPerTransaction: 5000 + maxSpendPerDay: 25000 + maxTransactionsPerDay: 20 + 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: @@ -28565,6 +28591,7 @@ components: enum: - PENDING_KYC - PROCESSING + - PENDING_AUTH - ACTIVE - FROZEN - CLOSED @@ -28575,22 +28602,25 @@ components: |-------|-------------| | `PENDING_KYC` | The cardholder has not yet completed KYC. Cards in this status cannot transact. | | `PROCESSING` | The card has been requested and is being provisioned with the issuer. | + | `PENDING_AUTH` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source lacks an active delegated signing key. Complete `POST /auth/delegated-keys` before using the card. | | `ACTIVE` | The card is live and can authorize transactions. | | `FROZEN` | The card is temporarily disabled by the platform. New authorizations are declined with `cardDeclinedReason: CARD_NOT_ACTIVE`. Existing settlements and refunds continue to reconcile. | | `CLOSED` | The card is permanently closed. Terminal, irreversible status. | CardStatusReason: type: string enum: + - DELEGATION_REQUIRED - ISSUER_REJECTED - CLOSED_BY_PLATFORM - CLOSED_BY_GRID description: | Reason a card reached a terminal or non-active status. Present on - `CLOSED` cards, and on cards that fail provisioning before reaching + `PENDING_AUTH` and `CLOSED` cards, and on cards that fail provisioning before reaching `ACTIVE`. | Reason | Description | |--------|-------------| + | `DELEGATION_REQUIRED` | The card funding source has no usable delegated signing key. Present while the card is `PENDING_AUTH`. | | `ISSUER_REJECTED` | The card issuer rejected provisioning during `PROCESSING`. | | `CLOSED_BY_PLATFORM` | The card was closed via `PATCH /cards/{id}` (`status: CLOSED`) by the platform. | | `CLOSED_BY_GRID` | The card was closed by Grid (e.g. compliance or risk action). | @@ -28657,7 +28687,7 @@ components: $ref: '#/components/schemas/CardStatus' statusReason: $ref: '#/components/schemas/CardStatusReason' - description: Reason associated with the current `status`. Present when the card is `CLOSED` or when provisioning was rejected; absent otherwise. + description: Reason associated with the current `status`. Present when the card is `PENDING_AUTH`, `CLOSED`, or when provisioning was rejected; absent otherwise. brand: $ref: '#/components/schemas/CardBrand' form: @@ -28802,7 +28832,7 @@ components: example: 20 CardUpdateRequest: type: object - description: Update request for `PATCH /cards/{id}`. At least one of `status`, `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` 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 `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`. + description: Update request for `PATCH /cards/{id}`. At least one of `status`, `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Explicit activation requires completed delegated signing authorization; otherwise it returns `409 INVALID_STATE_TRANSITION`. Funding-source changes reconcile `ACTIVE` / `PENDING_AUTH` automatically. `PENDING_AUTH` cannot be requested. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`. properties: status: type: string @@ -28810,7 +28840,7 @@ components: - ACTIVE - FROZEN - CLOSED - description: Target status 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. + description: Target status for the card. Permitted transitions are `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Activation requires completed delegated signing authorization. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. example: FROZEN substatus: type: string diff --git a/mintlify/snippets/cards/embedded-wallet-funding.mdx b/mintlify/snippets/cards/embedded-wallet-funding.mdx index 2fc72263b..481fa4928 100644 --- a/mintlify/snippets/cards/embedded-wallet-funding.mdx +++ b/mintlify/snippets/cards/embedded-wallet-funding.mdx @@ -61,11 +61,11 @@ curl -X POST "$GRID_BASE_URL/cards" \ The card's `currency` is `USD`. The wallet holds USDB, which Grid converts 1:1, so a $12.50 authorization pulls 12.50 USDB. -The card comes back in `status: "PROCESSING"` and activates through the +The card comes back in `status: "PROCESSING"` and moves to `PENDING_AUTH` through the `CARD.STATUS_CHANGE` webhook as described in -[Issuing cards](/cards/card-management/issuing-cards). An `ACTIVE` card with -no active delegated key declines every authorization, so complete the next -section before you show the card to the cardholder. +[Issuing cards](/cards/card-management/issuing-cards). Complete the next +section to authorize funding and activate the card before you show it to +the cardholder. Set `maxSpendPerTransaction` and `maxSpendPerDay`. Spend controls work the same way for a wallet-funded card as for any other card: the values on the card cap @@ -231,29 +231,30 @@ Two behaviors differ from a custodial funding source: ## What fails without an active key -Grid checks for the delegated key when it goes to sign the pull, not before. -If the card's funding source has no `ACTIVE` key (never created, still -`PENDING`, or revoked), the signing step fails and Grid declines the -authorization. Nothing is pulled from the wallet. +If an otherwise enabled card's funding source has no `ACTIVE` key (never +created, still `PENDING`, or revoked), the provisioned card is `PENDING_AUTH` with +`statusReason: "DELEGATION_REQUIRED"`. Grid declines new synchronous +authorizations before pulling funds from the wallet. The card transaction is recorded with `status: "DECLINED"` and -`cardDeclinedReason: "OTHER"`, and a `CARD_TRANSACTION.DECLINED` webhook -fires. The decline does not name the missing key, so check for it directly -when you investigate a declined card funded from a wallet: +`cardDeclinedReason: "CARD_NOT_ACTIVE"`, and a `CARD_TRANSACTION.DECLINED` +webhook fires. Check the card's `statusReason` and its funding source's +delegated keys when you investigate a pending wallet-funded card: ```bash curl -X GET "$GRID_BASE_URL/auth/delegated-keys?fundingSourceId=CardFundingSource:019542f5-b3e7-1d02-0000-000000000011" \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" ``` -An empty `data` array, or one with no `ACTIVE` entry, means every -authorization on that card declines until the cardholder completes the -three-leg flow again. +An empty `data` array, or one with no `ACTIVE` entry, means the cardholder +must complete the three-leg flow, including both signed retries. Once the +new key is `ACTIVE`, the pending card becomes `ACTIVE` automatically and +emits `CARD.STATUS_CHANGE`. -The same decline path applies when the cardholder revokes the key: the card -stays `ACTIVE`, and every authorization declines. To stop a card from -spending without touching the key, freeze it with `PATCH /cards/{id}` and -`status: "FROZEN"`. +Revoking the required key moves an active card to `PENDING_AUTH` and emits +`CARD.STATUS_CHANGE`. Processing, frozen, and closed cards retain their +status. To stop a card from spending without touching the key, freeze it +with `PATCH /cards/{id}` and `status: "FROZEN"`. ## Manage the key diff --git a/mintlify/snippets/cards/funding-sources.mdx b/mintlify/snippets/cards/funding-sources.mdx index 38c59d7a1..935ba460c 100644 --- a/mintlify/snippets/cards/funding-sources.mdx +++ b/mintlify/snippets/cards/funding-sources.mdx @@ -74,3 +74,11 @@ curl -X PATCH "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010" \ To permanently retire a card, close it with `PATCH /cards/{id}` and `status: "CLOSED"`. + +## Funding authorization and readiness + +Replacing USD funding with a USDB Embedded Wallet without an active +delegated key moves an active card to `PENDING_AUTH`. Complete all signed retries +of `POST /auth/delegated-keys` to restore `ACTIVE`. A key for another card or +funding source does not authorize this binding. Frozen and closed cards +retain their status. diff --git a/mintlify/snippets/cards/implementation-overview.mdx b/mintlify/snippets/cards/implementation-overview.mdx index 00d06383c..513fefafe 100644 --- a/mintlify/snippets/cards/implementation-overview.mdx +++ b/mintlify/snippets/cards/implementation-overview.mdx @@ -114,3 +114,11 @@ When you're ready to go live: Contact your Lightspark representative to enable Production card issuance and finalize issuer activations. + +## USDB funding authorization + +A provisioned card funded by a USDB Embedded Wallet is `PENDING_AUTH` until all +signed retries of `POST /auth/delegated-keys` are complete. Successful +delegation restores `ACTIVE`; funding changes or key revocation can return +an active card to `PENDING_AUTH`. Frozen and closed cards keep their status when +delegation completes. diff --git a/mintlify/snippets/cards/intro.mdx b/mintlify/snippets/cards/intro.mdx index 7bade4f90..f1af96990 100644 --- a/mintlify/snippets/cards/intro.mdx +++ b/mintlify/snippets/cards/intro.mdx @@ -24,12 +24,13 @@ See [Card PINs](/cards/card-management/pins) to let cardholders set, change, or ## Card lifecycle at a glance -A card moves through five states: +A card moves through six states: | State | Meaning | |-------|---------| | `PENDING_KYC` | Cardholder has not finished KYC; the card cannot transact yet. | | `PROCESSING` | Card has been requested and is being provisioned with the issuer. | +| `PENDING_AUTH` | Issuer provisioning is complete; USDB Embedded Wallet funding requires delegated signing authorization. | | `ACTIVE` | Card is live and can authorize transactions. | | `FROZEN` | Card is temporarily disabled. New authorizations are declined; in-flight settlements continue. | | `CLOSED` | Card is permanently closed. Terminal, irreversible. | diff --git a/mintlify/snippets/cards/issuing-cards.mdx b/mintlify/snippets/cards/issuing-cards.mdx index f2faacc21..338bdf2c0 100644 --- a/mintlify/snippets/cards/issuing-cards.mdx +++ b/mintlify/snippets/cards/issuing-cards.mdx @@ -38,14 +38,19 @@ spending-limit units. ## The lifecycle ```text -PROCESSING ──► ACTIVE ◄──► FROZEN - │ - └──► CLOSED +USD: PROCESSING ──► ACTIVE ◄──► FROZEN + │ │ + └───────────┴──► CLOSED + +USDB: PROCESSING ──► PENDING_AUTH ◄──► ACTIVE ◄──► FROZEN + │ │ │ + └─────────────────┴───────────┴──► CLOSED ``` | State | When you see it | |-------|-----------------| | `PROCESSING` | Returned synchronously from `POST /cards`. The card cannot transact yet. | +| `PENDING_AUTH` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source has no active delegated key. Complete delegated-key setup. | | `ACTIVE` | Issuer provisioned the card. Reached via `CARD.STATUS_CHANGE` webhook. | | `FROZEN` | You called `PATCH /cards/{id}` with `status: "FROZEN"`. | | `CLOSED` | You called `PATCH /cards/{id}` with `status: "CLOSED"`. Terminal. | @@ -60,6 +65,20 @@ issuer provisions the card asynchronously; on success a `CARD.STATUS_CHANGE` webhook fires with the activated `Card` resource including the populated `last4`, `expMonth`, and `expYear`. +For USDB Embedded Wallet funding without an active delegated key, the +provisioning webhook reports `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"`. +Complete all three calls to `POST /auth/delegated-keys`, including both signed +retries. When the final retry returns an `ACTIVE` delegated key, the pending +card becomes `ACTIVE` automatically and emits `CARD.STATUS_CHANGE`. + +USD funding does not require delegation. If delegation completes before +provisioning, the card can go directly from `PROCESSING` to `ACTIVE`. + +Replacing USD funding with USDB lacking an active key can move an active +card back to `PENDING_AUTH`. Switching back to USD, or completing delegation, +restores readiness. Revoking the required key also moves an active card to +`PENDING_AUTH`. Frozen and closed cards retain their status. + ## Revealing the PAN To show the cardholder their full PAN, CVV, and expiry, request a diff --git a/mintlify/snippets/cards/quickstart.mdx b/mintlify/snippets/cards/quickstart.mdx index c2021c736..e03232409 100644 --- a/mintlify/snippets/cards/quickstart.mdx +++ b/mintlify/snippets/cards/quickstart.mdx @@ -135,3 +135,11 @@ event model. You've issued a card, watched it activate, and driven an over-auth transaction through pull and clearing. + +## USDB funding authorization + +A provisioned card funded by a USDB Embedded Wallet is `PENDING_AUTH` until all +signed retries of `POST /auth/delegated-keys` are complete. Successful +delegation restores `ACTIVE`; funding changes or key revocation can return +an active card to `PENDING_AUTH`. Frozen and closed cards keep their status when +delegation completes. diff --git a/mintlify/snippets/cards/webhooks.mdx b/mintlify/snippets/cards/webhooks.mdx index 96577ca12..05e5673f3 100644 --- a/mintlify/snippets/cards/webhooks.mdx +++ b/mintlify/snippets/cards/webhooks.mdx @@ -11,7 +11,7 @@ lifecycle. | Type | Fires on | |------|----------| -| `CARD.STATUS_CHANGE` | `PROCESSING → ACTIVE`, `→ CLOSED (ISSUER_REJECTED)`, and every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. | +| `CARD.STATUS_CHANGE` | `PROCESSING → ACTIVE / PENDING_AUTH`, `ACTIVE ⇄ PENDING_AUTH`, `→ CLOSED (ISSUER_REJECTED)`, and every subsequent `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → FROZEN`, and `→ CLOSED` transition. | | `CARD.PIN_STATUS_CHANGE` | Grid records a new PIN status, such as `NOT_SET → OK`, `OK → BLOCKED`, or `BLOCKED → OK`. | | `CARD_TRANSACTION.AUTHORIZED` | An authorization is approved and a hold is placed on the funding source. | | `CARD_TRANSACTION.PARTIALLY_SETTLED` | A clearing posted, but more are still expected. | @@ -64,7 +64,8 @@ activation after issuance: Common branches to handle in your consumer: -- `status: "ACTIVE"` after `PROCESSING` — the card is live. To reveal +- `status: "PENDING_AUTH"` — complete delegated-key setup for the funding source. +- `status: "ACTIVE"` after `PROCESSING` or `PENDING_AUTH` — the card is live. To reveal the full card details, request a reveal with `POST /cards/{id}/reveal` right before rendering its short-lived `panEmbedUrl` in an iframe — webhook payloads never carry a reveal diff --git a/openapi.yaml b/openapi.yaml index ed1ad3019..93887e1aa 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -7482,7 +7482,7 @@ paths: 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. + 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. A provisioned `PENDING_AUTH` card using this funding source becomes `ACTIVE` automatically. Cards still provisioning remain `PROCESSING`; frozen and closed cards retain their 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. @@ -7669,7 +7669,7 @@ paths: 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. + Revoke an `ACTIVE` delegated signing key. An active card using this funding source becomes `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"`. Frozen and closed cards retain their status. 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 @@ -9125,7 +9125,7 @@ paths: A platform may be limited to a maximum number of live cards. Once that limit is reached, further issuance is rejected with `CARD_LIMIT_REACHED` until a card is closed or Lightspark raises the limit. Cards in `CLOSED` status do not count toward the limit. - New cards start in `status: "PROCESSING"` while the card issuer provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). + New cards start in `status: "PROCESSING"` while the card issuer provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). A provisioned card funded by a USDB Embedded Wallet remains `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"` until all signed retries of `POST /auth/delegated-keys` are complete. Replacing USD funding with USDB without an active key, or revoking the required key, also moves an active card to `PENDING_AUTH`. Completing delegation or switching back to USD restores `ACTIVE`; frozen and closed cards keep their status. operationId: createCard tags: - Cards @@ -9337,8 +9337,8 @@ paths: description: | Update a card's `status`, bound `fundingSource`, and / or `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`, or set a new `threeDSecurePassword`. At least one field must be supplied. - - `status` transitions are limited to `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Any other transition returns `409 INVALID_STATE_TRANSITION`. - - `fundingSource`, when supplied, replaces the card's bound internal account. It must belong to the customer and be denominated in the card's currency. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate requests. On card programs where Grid makes the authorization decision, the combination remains valid for `status` changes other than `CLOSED`. + - `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Activating a card whose USDB funding source lacks a usable delegated key returns `409 INVALID_STATE_TRANSITION`. `PENDING_AUTH` is managed by Grid and cannot be requested. Completing delegation does not unfreeze or reopen a card. + - `fundingSource`, when supplied, replaces the card's bound internal account. It must belong to the customer and use a currency supported by the card program, including USDB for USD cards. An active card becomes `PENDING_AUTH` when switched to USDB without an active delegated key. Switching back to USD restores readiness. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate requests. On card programs where Grid makes the authorization decision, the combination remains valid for `status` changes other than `CLOSED`. - `maxSpendPerTransaction` sets the largest amount the card can authorize on a single transaction. An authorization for exactly the limit is allowed, and a later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. Send a positive integer in the smallest unit of the card's currency (cents for USD) to set the limit, or null to remove it. If your platform config sets `cardConfigs.maxSpendPerTransaction`, the lower of the two applies. You can only send this when the card's `cardCapabilities.supportsSpendLimits` is true, and not together with `status: CLOSED`. - `maxSpendPerDay`, when supplied, replaces the card-specific cap on cumulative new spend during one UTC calendar day. Supply a positive integer in the smallest unit of the card's currency to set it or null to clear it. If the platform config sets `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. The card's `cardCapabilities.supportsSpendLimits` must be true. `maxSpendPerDay` cannot be supplied alongside `status: CLOSED`. - `maxTransactionsPerDay`, when supplied, replaces the card-specific cap on the number of transactions the card may authorize during one UTC calendar day. Supply a positive integer to set it or null to clear it. If the platform config sets `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. `maxTransactionsPerDay` requires the card's `cardCapabilities.supportsTransactionCountLimit` to be true and cannot be supplied alongside `status: CLOSED`. @@ -9431,7 +9431,7 @@ paths: schema: $ref: '#/components/schemas/Error404' '409': - description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` 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 `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' + description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` transition is not one of `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, or `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`, or when activation is requested before delegated signing authorization is complete; with `CARD_ALREADY_CLOSED` when `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.' content: application/json: schema: @@ -13160,7 +13160,7 @@ webhooks: post: summary: Card status change description: | - Webhook that is called when a card's lifecycle status changes. Fires on `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. + Webhook that is called when a card's lifecycle status changes. Fires on `PROCESSING → ACTIVE | PENDING_AUTH`, `ACTIVE ⇄ PENDING_AUTH` as funding sources or delegated signing authorization change, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → FROZEN`, and `→ CLOSED` transition. This endpoint should be implemented by clients of the Grid API. @@ -13211,6 +13211,32 @@ webhooks: issuerRef: lead_card_7a1b9c3d createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:11:00Z' + delegationRequired: + summary: Provisioned card requires funding authorization + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATUS_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-001 + status: PENDING_AUTH + statusReason: DELEGATION_REQUIRED + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + maxSpendPerTransaction: 5000 + maxSpendPerDay: 25000 + maxTransactionsPerDay: 20 + 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: @@ -28565,6 +28591,7 @@ components: enum: - PENDING_KYC - PROCESSING + - PENDING_AUTH - ACTIVE - FROZEN - CLOSED @@ -28575,22 +28602,25 @@ components: |-------|-------------| | `PENDING_KYC` | The cardholder has not yet completed KYC. Cards in this status cannot transact. | | `PROCESSING` | The card has been requested and is being provisioned with the issuer. | + | `PENDING_AUTH` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source lacks an active delegated signing key. Complete `POST /auth/delegated-keys` before using the card. | | `ACTIVE` | The card is live and can authorize transactions. | | `FROZEN` | The card is temporarily disabled by the platform. New authorizations are declined with `cardDeclinedReason: CARD_NOT_ACTIVE`. Existing settlements and refunds continue to reconcile. | | `CLOSED` | The card is permanently closed. Terminal, irreversible status. | CardStatusReason: type: string enum: + - DELEGATION_REQUIRED - ISSUER_REJECTED - CLOSED_BY_PLATFORM - CLOSED_BY_GRID description: | Reason a card reached a terminal or non-active status. Present on - `CLOSED` cards, and on cards that fail provisioning before reaching + `PENDING_AUTH` and `CLOSED` cards, and on cards that fail provisioning before reaching `ACTIVE`. | Reason | Description | |--------|-------------| + | `DELEGATION_REQUIRED` | The card funding source has no usable delegated signing key. Present while the card is `PENDING_AUTH`. | | `ISSUER_REJECTED` | The card issuer rejected provisioning during `PROCESSING`. | | `CLOSED_BY_PLATFORM` | The card was closed via `PATCH /cards/{id}` (`status: CLOSED`) by the platform. | | `CLOSED_BY_GRID` | The card was closed by Grid (e.g. compliance or risk action). | @@ -28657,7 +28687,7 @@ components: $ref: '#/components/schemas/CardStatus' statusReason: $ref: '#/components/schemas/CardStatusReason' - description: Reason associated with the current `status`. Present when the card is `CLOSED` or when provisioning was rejected; absent otherwise. + description: Reason associated with the current `status`. Present when the card is `PENDING_AUTH`, `CLOSED`, or when provisioning was rejected; absent otherwise. brand: $ref: '#/components/schemas/CardBrand' form: @@ -28802,7 +28832,7 @@ components: example: 20 CardUpdateRequest: type: object - description: Update request for `PATCH /cards/{id}`. At least one of `status`, `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` 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 `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`. + description: Update request for `PATCH /cards/{id}`. At least one of `status`, `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Explicit activation requires completed delegated signing authorization; otherwise it returns `409 INVALID_STATE_TRANSITION`. Funding-source changes reconcile `ACTIVE` / `PENDING_AUTH` automatically. `PENDING_AUTH` cannot be requested. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`. properties: status: type: string @@ -28810,7 +28840,7 @@ components: - ACTIVE - FROZEN - CLOSED - description: Target status 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. + description: Target status for the card. Permitted transitions are `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Activation requires completed delegated signing authorization. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. example: FROZEN substatus: type: string diff --git a/openapi/components/schemas/cards/Card.yaml b/openapi/components/schemas/cards/Card.yaml index acee24d46..cbebf0301 100644 --- a/openapi/components/schemas/cards/Card.yaml +++ b/openapi/components/schemas/cards/Card.yaml @@ -30,7 +30,7 @@ properties: $ref: ./CardStatusReason.yaml description: >- Reason associated with the current `status`. Present when the card is - `CLOSED` or when provisioning was rejected; absent otherwise. + `PENDING_AUTH`, `CLOSED`, or when provisioning was rejected; absent otherwise. brand: $ref: ./CardBrand.yaml form: diff --git a/openapi/components/schemas/cards/CardStatus.yaml b/openapi/components/schemas/cards/CardStatus.yaml index d8de701fb..7fe664adb 100644 --- a/openapi/components/schemas/cards/CardStatus.yaml +++ b/openapi/components/schemas/cards/CardStatus.yaml @@ -2,6 +2,7 @@ type: string enum: - PENDING_KYC - PROCESSING + - PENDING_AUTH - ACTIVE - FROZEN - CLOSED @@ -12,6 +13,7 @@ description: | |-------|-------------| | `PENDING_KYC` | The cardholder has not yet completed KYC. Cards in this status cannot transact. | | `PROCESSING` | The card has been requested and is being provisioned with the issuer. | + | `PENDING_AUTH` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source lacks an active delegated signing key. Complete `POST /auth/delegated-keys` before using the card. | | `ACTIVE` | The card is live and can authorize transactions. | | `FROZEN` | The card is temporarily disabled by the platform. New authorizations are declined with `cardDeclinedReason: CARD_NOT_ACTIVE`. Existing settlements and refunds continue to reconcile. | | `CLOSED` | The card is permanently closed. Terminal, irreversible status. | diff --git a/openapi/components/schemas/cards/CardStatusReason.yaml b/openapi/components/schemas/cards/CardStatusReason.yaml index 965757712..02f45d5ff 100644 --- a/openapi/components/schemas/cards/CardStatusReason.yaml +++ b/openapi/components/schemas/cards/CardStatusReason.yaml @@ -1,15 +1,17 @@ type: string enum: + - DELEGATION_REQUIRED - ISSUER_REJECTED - CLOSED_BY_PLATFORM - CLOSED_BY_GRID description: | Reason a card reached a terminal or non-active status. Present on - `CLOSED` cards, and on cards that fail provisioning before reaching + `PENDING_AUTH` and `CLOSED` cards, and on cards that fail provisioning before reaching `ACTIVE`. | Reason | Description | |--------|-------------| + | `DELEGATION_REQUIRED` | The card funding source has no usable delegated signing key. Present while the card is `PENDING_AUTH`. | | `ISSUER_REJECTED` | The card issuer rejected provisioning during `PROCESSING`. | | `CLOSED_BY_PLATFORM` | The card was closed via `PATCH /cards/{id}` (`status: CLOSED`) by the platform. | | `CLOSED_BY_GRID` | The card was closed by Grid (e.g. compliance or risk action). | diff --git a/openapi/components/schemas/cards/CardUpdateRequest.yaml b/openapi/components/schemas/cards/CardUpdateRequest.yaml index 5d4d37f1e..328120f3e 100644 --- a/openapi/components/schemas/cards/CardUpdateRequest.yaml +++ b/openapi/components/schemas/cards/CardUpdateRequest.yaml @@ -5,10 +5,12 @@ description: >- `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` - 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 `fundingSource`, `maxSpendPerTransaction`, + transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and + `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Explicit activation requires completed + delegated signing authorization; otherwise it returns + `409 INVALID_STATE_TRANSITION`. Funding-source changes reconcile + `ACTIVE` / `PENDING_AUTH` automatically. `PENDING_AUTH` cannot be requested. + `CLOSED` is terminal and irreversible and cannot be combined with `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`. properties: status: @@ -19,7 +21,9 @@ properties: - CLOSED description: >- Target status for the card. Permitted transitions are - `ACTIVE ⇄ FROZEN` and `ACTIVE | FROZEN → CLOSED`. `CLOSED` is + `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and + `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Activation requires completed + delegated signing authorization. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again. example: FROZEN diff --git a/openapi/paths/auth/auth_delegated-keys.yaml b/openapi/paths/auth/auth_delegated-keys.yaml index 5009d3de6..7500cc1df 100644 --- a/openapi/paths/auth/auth_delegated-keys.yaml +++ b/openapi/paths/auth/auth_delegated-keys.yaml @@ -27,7 +27,9 @@ post: 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. + with the created `DelegatedKey` in `ACTIVE` status. A provisioned `PENDING_AUTH` + card using this funding source becomes `ACTIVE` automatically. Cards still + provisioning remain `PROCESSING`; frozen and closed cards retain their status. The same request body must be sent on all three legs. A flow abandoned diff --git a/openapi/paths/auth/auth_delegated-keys_{id}.yaml b/openapi/paths/auth/auth_delegated-keys_{id}.yaml index 2b58efca4..8d7a3d665 100644 --- a/openapi/paths/auth/auth_delegated-keys_{id}.yaml +++ b/openapi/paths/auth/auth_delegated-keys_{id}.yaml @@ -45,7 +45,9 @@ get: delete: summary: Revoke a delegated signing key description: > - Revoke an `ACTIVE` delegated signing key. Grid uses the custodied + Revoke an `ACTIVE` delegated signing key. An active card using this funding + source becomes `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"`. Frozen + and closed cards retain their status. 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. diff --git a/openapi/paths/cards/cards.yaml b/openapi/paths/cards/cards.yaml index a181c02c9..c94170628 100644 --- a/openapi/paths/cards/cards.yaml +++ b/openapi/paths/cards/cards.yaml @@ -40,6 +40,13 @@ post: provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). + A provisioned card funded by a USDB Embedded Wallet remains `PENDING_AUTH` + with `statusReason: "DELEGATION_REQUIRED"` + until all signed retries of `POST /auth/delegated-keys` are complete. + Replacing USD funding with USDB without an active key, or revoking the + required key, also moves an active card to `PENDING_AUTH`. Completing delegation + or switching back to USD restores `ACTIVE`; frozen and closed cards keep + their status. operationId: createCard tags: - Cards diff --git a/openapi/paths/cards/cards_{id}.yaml b/openapi/paths/cards/cards_{id}.yaml index 5e23f294a..1161374e3 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -59,13 +59,18 @@ patch: or set a new `threeDSecurePassword`. At least one field must be supplied. - - `status` transitions are limited to `ACTIVE ⇄ FROZEN` and - `ACTIVE | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. - Any other transition returns `409 INVALID_STATE_TRANSITION`. + - `status` transitions include `ACTIVE ⇄ FROZEN`, + `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. + `CLOSED` is terminal and irreversible. Activating a card whose USDB funding + source lacks a usable delegated key returns `409 INVALID_STATE_TRANSITION`. + `PENDING_AUTH` is managed by Grid and cannot be requested. Completing delegation + does not unfreeze or reopen a card. - `fundingSource`, when supplied, replaces the card's bound internal - account. It must belong to the customer and be denominated in the card's - currency. `fundingSource` cannot be supplied alongside `status: CLOSED`. + account. It must belong to the customer and use a currency supported by the + card program, including USDB for USD cards. An active card becomes `PENDING_AUTH` + when switched to USDB without an active delegated key. Switching back to + USD restores readiness. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate @@ -231,8 +236,9 @@ patch: description: >- Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` 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 + `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, or + `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`, or when activation is requested + before delegated signing authorization is complete; with `CARD_ALREADY_CLOSED` when `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`. diff --git a/openapi/webhooks/card-status-change.yaml b/openapi/webhooks/card-status-change.yaml index 18083972b..1a9d70e05 100644 --- a/openapi/webhooks/card-status-change.yaml +++ b/openapi/webhooks/card-status-change.yaml @@ -2,9 +2,10 @@ post: summary: Card status change description: > Webhook that is called when a card's lifecycle status changes. Fires on - `PROCESSING → ACTIVE`, on `PROCESSING → CLOSED (ISSUER_REJECTED)` + `PROCESSING → ACTIVE | PENDING_AUTH`, `ACTIVE ⇄ PENDING_AUTH` as funding + sources or delegated signing authorization change, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent - `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. + `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → FROZEN`, and `→ CLOSED` transition. This endpoint should be implemented by clients of the Grid API. @@ -66,6 +67,32 @@ post: issuerRef: lead_card_7a1b9c3d createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:11:00Z' + delegationRequired: + summary: Provisioned card requires funding authorization + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000020 + type: CARD.STATUS_CHANGE + timestamp: '2026-05-08T14:11:00Z' + data: + id: Card:019542f5-b3e7-1d02-0000-000000000010 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCardId: card-emp-001 + status: PENDING_AUTH + statusReason: DELEGATION_REQUIRED + brand: VISA + form: VIRTUAL + last4: '4242' + expMonth: 12 + expYear: 2029 + fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + maxSpendPerTransaction: 5000 + maxSpendPerDay: 25000 + maxTransactionsPerDay: 20 + 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: