From c30da6baf9e49e8331f2a2ded4a5a82cb750f73a Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Wed, 12 Aug 2026 14:12:38 -0700 Subject: [PATCH] feat(grid-api): move card spending limits onto cards --- mintlify/openapi.yaml | 106 ++++++++++++------ openapi.yaml | 106 ++++++++++++------ .../components/schemas/auth/DelegatedKey.yaml | 8 -- .../auth/DelegatedKeyCreateRequest.yaml | 12 -- openapi/components/schemas/cards/Card.yaml | 13 +++ .../schemas/cards/CardCreateRequest.yaml | 16 ++- .../CardSpendingLimit.yaml} | 11 +- .../schemas/cards/CardUpdateRequest.yaml | 26 ++++- openapi/paths/cards/cards.yaml | 10 ++ openapi/paths/cards/cards_{id}.yaml | 21 +++- .../webhooks/card-funding-source-change.yaml | 1 + openapi/webhooks/card-state-change.yaml | 7 ++ 12 files changed, 238 insertions(+), 99 deletions(-) rename openapi/components/schemas/{auth/DelegatedKeySpendingLimit.yaml => cards/CardSpendingLimit.yaml} (50%) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index d2332c99e..3b5749aa2 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8076,6 +8076,8 @@ paths: 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`. + An optional `spendingLimits` entry sets the largest amount a single card transaction may authorize. The limit is enforced by Grid for card programs where Grid makes the authorization decision, whether the card is funded by an Embedded Wallet account or custodial fiat. Omit it or pass an empty array for no limit. + 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). @@ -8099,6 +8101,9 @@ paths: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 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. @@ -8270,10 +8275,11 @@ paths: patch: summary: Update a card description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. + Update a card's `state`, bound `fundingSources`, and / or `spendingLimits`. At least one field 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`. + - `spendingLimits`, when supplied, fully replaces the card's application-enforced per-transaction limit. Supply one entry to set it or an empty array to clear it. The entry's currency must match the card's currency. Limits are supported only for card programs where Grid makes the authorization decision. `spendingLimits` 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`): @@ -8329,6 +8335,16 @@ paths: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + setSpendingLimit: + summary: Set the card's per-transaction spending limit + value: + spendingLimits: + - currencyCode: USD + maxPerTransaction: 10000 + clearSpendingLimit: + summary: Remove the card's per-transaction spending limit + value: + spendingLimits: [] freezeAndUpdateSources: summary: Freeze the card and replace its funding sources in one call value: @@ -10878,6 +10894,9 @@ webhooks: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD processorRef: card_b81c2a4f issuerRef: lead_card_7a1b9c3d @@ -10897,6 +10916,7 @@ webhooks: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:12:00Z' @@ -10918,6 +10938,9 @@ webhooks: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-09T09:00:00Z' @@ -10992,6 +11015,7 @@ webhooks: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:30:00Z' @@ -22999,24 +23023,6 @@ components: - `ACTIVE`: The policy is granted and the key may stamp quote executions. - `REVOKED`: The delegated user has been deleted and the key can no longer sign. example: ACTIVE - DelegatedKeySpendingLimit: - title: Delegated Key Spending Limit - type: object - required: - - currencyCode - - maxPerTransaction - properties: - currencyCode: - type: string - pattern: ^[A-Z0-9]{3,16}$ - description: Uppercase alphanumeric currency code the limit applies to — ISO 4217 for fiat (e.g. USD), or a Grid token code for stablecoins (e.g. USDB). Must match the card's currency; requests with any other currency are rejected. - example: USD - maxPerTransaction: - type: integer - format: int64 - minimum: 1 - description: Largest amount a single card transaction may authorize, in the smallest unit of the currency (e.g., cents for USD). - example: 5000 DelegatedKey: title: Delegated Key type: object @@ -23058,12 +23064,6 @@ components: example: Settlement service key status: $ref: '#/components/schemas/DelegatedKeyStatus' - spendingLimits: - type: array - uniqueItems: true - description: Per-transaction spending limits the key was created with, at most one entry per currency. Absent when the key has no limits. - items: - $ref: '#/components/schemas/DelegatedKeySpendingLimit' createdAt: type: string format: date-time @@ -23107,12 +23107,6 @@ components: maxLength: 256 description: Human-readable label for the delegated key. example: Card payments key - spendingLimits: - type: array - uniqueItems: true - description: Optional per-transaction spending limits for the key, at most one entry per currency — a request with duplicate currency entries is rejected. Grid enforces the limits when authorizing card transactions funded by the key's Embedded Wallet account; a currency with no entry is unlimited. Immutable — revoke the key and create a new one to change limits. - items: - $ref: '#/components/schemas/DelegatedKeySpendingLimit' DelegatedKeySignedRequestChallenge: title: Delegated Key Signed Request Challenge description: 202 response returned from the delegated-key endpoints. Stamp `payloadToSign` with the session API keypair of a verified credential on the delegated key's Embedded Wallet funding account, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. @@ -23668,6 +23662,24 @@ components: description: | Physical form factor of the card. Only `VIRTUAL` is supported in v1; `PHYSICAL` will be added in a later release. + CardSpendingLimit: + title: Card Spending Limit + type: object + required: + - currencyCode + - maxPerTransaction + properties: + currencyCode: + type: string + pattern: ^[A-Z0-9]{3,16}$ + description: Uppercase alphanumeric code for the card's currency — ISO 4217 for fiat (for example, USD), or a Grid token code for stablecoins (for example, USDB). Must match the card's `currency`. + example: USD + maxPerTransaction: + type: integer + format: int64 + minimum: 1 + description: Largest amount a single card transaction may authorize, in the smallest unit of the card's currency (for example, cents for USD). A transaction for exactly this amount is allowed. + example: 5000 Card: type: object required: @@ -23676,6 +23688,7 @@ components: - state - form - fundingSources + - spendingLimits - createdAt - updatedAt properties: @@ -23725,6 +23738,15 @@ components: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: Per-transaction spending limit for this card. An empty array means the card has no application-enforced limit. Cards support at most one entry, whose `currencyCode` matches the card's `currency`. + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 5000 currency: type: string description: Currency the card transacts in (ISO 4217 for fiat, tickers for crypto). Derived from the funding sources at issue time — all funding sources bound to a card must be denominated in the same card-eligible currency. @@ -23795,15 +23817,24 @@ components: $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`. + 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; otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. minItems: 1 items: type: string example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + type: array + maxItems: 1 + description: Optional per-transaction spending limit for the card. Omit this field or supply an empty array for no limit. At most one entry may be supplied, and its `currencyCode` must match the currency derived from the card's funding sources. Spending limits are supported only for card programs whose authorization decisions are made by Grid. + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 5000 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. + description: Update request for `PATCH /cards/{id}`. At least one of `state`, `fundingSources`, or `spendingLimits` 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` or `spendingLimits`. `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 @@ -23822,6 +23853,15 @@ components: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: 'Replacement per-transaction spending limit for the card. Omit this field to leave the current limit unchanged, supply an empty array to clear it, or supply one entry to set it. The entry''s `currencyCode` must match the card''s `currency`. Spending limits are supported only for card programs whose authorization decisions are made by Grid. Cannot be supplied alongside `state: CLOSED`.' + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 10000 CardRevealResponse: type: object required: diff --git a/openapi.yaml b/openapi.yaml index d2332c99e..3b5749aa2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8076,6 +8076,8 @@ paths: 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`. + An optional `spendingLimits` entry sets the largest amount a single card transaction may authorize. The limit is enforced by Grid for card programs where Grid makes the authorization decision, whether the card is funded by an Embedded Wallet account or custodial fiat. Omit it or pass an empty array for no limit. + 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). @@ -8099,6 +8101,9 @@ paths: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 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. @@ -8270,10 +8275,11 @@ paths: patch: summary: Update a card description: | - Update a card's `state` and / or its bound `fundingSources`. At least one of the two fields must be supplied. + Update a card's `state`, bound `fundingSources`, and / or `spendingLimits`. At least one field 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`. + - `spendingLimits`, when supplied, fully replaces the card's application-enforced per-transaction limit. Supply one entry to set it or an empty array to clear it. The entry's currency must match the card's currency. Limits are supported only for card programs where Grid makes the authorization decision. `spendingLimits` 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`): @@ -8329,6 +8335,16 @@ paths: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + setSpendingLimit: + summary: Set the card's per-transaction spending limit + value: + spendingLimits: + - currencyCode: USD + maxPerTransaction: 10000 + clearSpendingLimit: + summary: Remove the card's per-transaction spending limit + value: + spendingLimits: [] freezeAndUpdateSources: summary: Freeze the card and replace its funding sources in one call value: @@ -10878,6 +10894,9 @@ webhooks: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD processorRef: card_b81c2a4f issuerRef: lead_card_7a1b9c3d @@ -10897,6 +10916,7 @@ webhooks: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:12:00Z' @@ -10918,6 +10938,9 @@ webhooks: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-09T09:00:00Z' @@ -10992,6 +11015,7 @@ webhooks: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:30:00Z' @@ -22999,24 +23023,6 @@ components: - `ACTIVE`: The policy is granted and the key may stamp quote executions. - `REVOKED`: The delegated user has been deleted and the key can no longer sign. example: ACTIVE - DelegatedKeySpendingLimit: - title: Delegated Key Spending Limit - type: object - required: - - currencyCode - - maxPerTransaction - properties: - currencyCode: - type: string - pattern: ^[A-Z0-9]{3,16}$ - description: Uppercase alphanumeric currency code the limit applies to — ISO 4217 for fiat (e.g. USD), or a Grid token code for stablecoins (e.g. USDB). Must match the card's currency; requests with any other currency are rejected. - example: USD - maxPerTransaction: - type: integer - format: int64 - minimum: 1 - description: Largest amount a single card transaction may authorize, in the smallest unit of the currency (e.g., cents for USD). - example: 5000 DelegatedKey: title: Delegated Key type: object @@ -23058,12 +23064,6 @@ components: example: Settlement service key status: $ref: '#/components/schemas/DelegatedKeyStatus' - spendingLimits: - type: array - uniqueItems: true - description: Per-transaction spending limits the key was created with, at most one entry per currency. Absent when the key has no limits. - items: - $ref: '#/components/schemas/DelegatedKeySpendingLimit' createdAt: type: string format: date-time @@ -23107,12 +23107,6 @@ components: maxLength: 256 description: Human-readable label for the delegated key. example: Card payments key - spendingLimits: - type: array - uniqueItems: true - description: Optional per-transaction spending limits for the key, at most one entry per currency — a request with duplicate currency entries is rejected. Grid enforces the limits when authorizing card transactions funded by the key's Embedded Wallet account; a currency with no entry is unlimited. Immutable — revoke the key and create a new one to change limits. - items: - $ref: '#/components/schemas/DelegatedKeySpendingLimit' DelegatedKeySignedRequestChallenge: title: Delegated Key Signed Request Challenge description: 202 response returned from the delegated-key endpoints. Stamp `payloadToSign` with the session API keypair of a verified credential on the delegated key's Embedded Wallet funding account, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. @@ -23668,6 +23662,24 @@ components: description: | Physical form factor of the card. Only `VIRTUAL` is supported in v1; `PHYSICAL` will be added in a later release. + CardSpendingLimit: + title: Card Spending Limit + type: object + required: + - currencyCode + - maxPerTransaction + properties: + currencyCode: + type: string + pattern: ^[A-Z0-9]{3,16}$ + description: Uppercase alphanumeric code for the card's currency — ISO 4217 for fiat (for example, USD), or a Grid token code for stablecoins (for example, USDB). Must match the card's `currency`. + example: USD + maxPerTransaction: + type: integer + format: int64 + minimum: 1 + description: Largest amount a single card transaction may authorize, in the smallest unit of the card's currency (for example, cents for USD). A transaction for exactly this amount is allowed. + example: 5000 Card: type: object required: @@ -23676,6 +23688,7 @@ components: - state - form - fundingSources + - spendingLimits - createdAt - updatedAt properties: @@ -23725,6 +23738,15 @@ components: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: Per-transaction spending limit for this card. An empty array means the card has no application-enforced limit. Cards support at most one entry, whose `currencyCode` matches the card's `currency`. + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 5000 currency: type: string description: Currency the card transacts in (ISO 4217 for fiat, tickers for crypto). Derived from the funding sources at issue time — all funding sources bound to a card must be denominated in the same card-eligible currency. @@ -23795,15 +23817,24 @@ components: $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`. + 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; otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. minItems: 1 items: type: string example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + type: array + maxItems: 1 + description: Optional per-transaction spending limit for the card. Omit this field or supply an empty array for no limit. At most one entry may be supplied, and its `currencyCode` must match the currency derived from the card's funding sources. Spending limits are supported only for card programs whose authorization decisions are made by Grid. + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 5000 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. + description: Update request for `PATCH /cards/{id}`. At least one of `state`, `fundingSources`, or `spendingLimits` 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` or `spendingLimits`. `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 @@ -23822,6 +23853,15 @@ components: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: 'Replacement per-transaction spending limit for the card. Omit this field to leave the current limit unchanged, supply an empty array to clear it, or supply one entry to set it. The entry''s `currencyCode` must match the card''s `currency`. Spending limits are supported only for card programs whose authorization decisions are made by Grid. Cannot be supplied alongside `state: CLOSED`.' + items: + $ref: '#/components/schemas/CardSpendingLimit' + example: + - currencyCode: USD + maxPerTransaction: 10000 CardRevealResponse: type: object required: diff --git a/openapi/components/schemas/auth/DelegatedKey.yaml b/openapi/components/schemas/auth/DelegatedKey.yaml index aef934856..9f643eb63 100644 --- a/openapi/components/schemas/auth/DelegatedKey.yaml +++ b/openapi/components/schemas/auth/DelegatedKey.yaml @@ -48,14 +48,6 @@ properties: example: Settlement service key status: $ref: ./DelegatedKeyStatus.yaml - spendingLimits: - type: array - uniqueItems: true - description: >- - Per-transaction spending limits the key was created with, at most one - entry per currency. Absent when the key has no limits. - items: - $ref: ./DelegatedKeySpendingLimit.yaml createdAt: type: string format: date-time diff --git a/openapi/components/schemas/auth/DelegatedKeyCreateRequest.yaml b/openapi/components/schemas/auth/DelegatedKeyCreateRequest.yaml index 0f48939a9..be84ac495 100644 --- a/openapi/components/schemas/auth/DelegatedKeyCreateRequest.yaml +++ b/openapi/components/schemas/auth/DelegatedKeyCreateRequest.yaml @@ -24,15 +24,3 @@ properties: maxLength: 256 description: Human-readable label for the delegated key. example: Card payments key - spendingLimits: - type: array - uniqueItems: true - description: >- - Optional per-transaction spending limits for the key, at most one entry - per currency — a request with duplicate currency entries is rejected. - Grid enforces the limits when authorizing card transactions funded by - the key's Embedded Wallet account; a currency with no entry is - unlimited. Immutable — revoke the key and create a new one to change - limits. - items: - $ref: ./DelegatedKeySpendingLimit.yaml diff --git a/openapi/components/schemas/cards/Card.yaml b/openapi/components/schemas/cards/Card.yaml index f9448354d..74aef830d 100644 --- a/openapi/components/schemas/cards/Card.yaml +++ b/openapi/components/schemas/cards/Card.yaml @@ -5,6 +5,7 @@ required: - state - form - fundingSources + - spendingLimits - createdAt - updatedAt properties: @@ -61,6 +62,18 @@ properties: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: >- + Per-transaction spending limit for this card. An empty array means the + card has no application-enforced limit. Cards support at most one entry, + whose `currencyCode` matches the card's `currency`. + items: + $ref: ./CardSpendingLimit.yaml + example: + - currencyCode: USD + maxPerTransaction: 5000 currency: type: string description: >- diff --git a/openapi/components/schemas/cards/CardCreateRequest.yaml b/openapi/components/schemas/cards/CardCreateRequest.yaml index 3de99ac8a..0c1e9062d 100644 --- a/openapi/components/schemas/cards/CardCreateRequest.yaml +++ b/openapi/components/schemas/cards/CardCreateRequest.yaml @@ -36,10 +36,24 @@ properties: 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 + currency; otherwise the request is rejected with `FUNDING_SOURCE_INELIGIBLE`. minItems: 1 items: type: string example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + type: array + maxItems: 1 + description: >- + Optional per-transaction spending limit for the card. Omit this field or + supply an empty array for no limit. At most one entry may be supplied, + and its `currencyCode` must match the currency derived from the card's + funding sources. Spending limits are supported only for card programs + whose authorization decisions are made by Grid. + items: + $ref: ./CardSpendingLimit.yaml + example: + - currencyCode: USD + maxPerTransaction: 5000 diff --git a/openapi/components/schemas/auth/DelegatedKeySpendingLimit.yaml b/openapi/components/schemas/cards/CardSpendingLimit.yaml similarity index 50% rename from openapi/components/schemas/auth/DelegatedKeySpendingLimit.yaml rename to openapi/components/schemas/cards/CardSpendingLimit.yaml index 908691f0d..2d4d385c2 100644 --- a/openapi/components/schemas/auth/DelegatedKeySpendingLimit.yaml +++ b/openapi/components/schemas/cards/CardSpendingLimit.yaml @@ -1,4 +1,4 @@ -title: Delegated Key Spending Limit +title: Card Spending Limit type: object required: - currencyCode @@ -8,9 +8,9 @@ properties: type: string pattern: '^[A-Z0-9]{3,16}$' description: >- - Uppercase alphanumeric currency code the limit applies to — ISO 4217 for - fiat (e.g. USD), or a Grid token code for stablecoins (e.g. USDB). Must - match the card's currency; requests with any other currency are rejected. + Uppercase alphanumeric code for the card's currency — ISO 4217 for fiat + (for example, USD), or a Grid token code for stablecoins (for example, + USDB). Must match the card's `currency`. example: USD maxPerTransaction: type: integer @@ -18,5 +18,6 @@ properties: minimum: 1 description: >- Largest amount a single card transaction may authorize, in the smallest - unit of the currency (e.g., cents for USD). + unit of the card's currency (for example, cents for USD). A transaction + for exactly this amount is allowed. example: 5000 diff --git a/openapi/components/schemas/cards/CardUpdateRequest.yaml b/openapi/components/schemas/cards/CardUpdateRequest.yaml index 4664e3ecb..1b9ccdbcc 100644 --- a/openapi/components/schemas/cards/CardUpdateRequest.yaml +++ b/openapi/components/schemas/cards/CardUpdateRequest.yaml @@ -1,10 +1,11 @@ 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`. + Update request for `PATCH /cards/{id}`. At least one of `state`, + `fundingSources`, or `spendingLimits` 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` or + `spendingLimits`. `fundingSources`, when supplied, fully replaces the card's bound funding sources — the array order determines the priority Authorization Decisioning tries them in. @@ -36,3 +37,18 @@ properties: example: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: + type: array + maxItems: 1 + description: >- + Replacement per-transaction spending limit for the card. Omit this field + to leave the current limit unchanged, supply an empty array to clear it, + or supply one entry to set it. The entry's `currencyCode` must match the + card's `currency`. Spending limits are supported only for card programs + whose authorization decisions are made by Grid. Cannot be supplied + alongside `state: CLOSED`. + items: + $ref: ./CardSpendingLimit.yaml + example: + - currencyCode: USD + maxPerTransaction: 10000 diff --git a/openapi/paths/cards/cards.yaml b/openapi/paths/cards/cards.yaml index 9d945fc07..5e9441461 100644 --- a/openapi/paths/cards/cards.yaml +++ b/openapi/paths/cards/cards.yaml @@ -7,6 +7,13 @@ post: with `CARDHOLDER_KYC_NOT_APPROVED`. + An optional `spendingLimits` entry sets the largest amount a single card + transaction may authorize. The limit is enforced by Grid for card programs + where Grid makes the authorization decision, whether the card is funded by + an Embedded Wallet account or custodial fiat. Omit it or pass an empty + array for no limit. + + 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 @@ -39,6 +46,9 @@ post: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 responses: '201': description: >- diff --git a/openapi/paths/cards/cards_{id}.yaml b/openapi/paths/cards/cards_{id}.yaml index c5149f206..5477337d6 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -54,8 +54,8 @@ get: patch: summary: Update a card description: > - Update a card's `state` and / or its bound `fundingSources`. At least - one of the two fields must be supplied. + Update a card's `state`, bound `fundingSources`, and / or + `spendingLimits`. At least one field must be supplied. - `state` transitions are limited to `ACTIVE ⇄ FROZEN` and @@ -69,6 +69,13 @@ patch: one source. `fundingSources` cannot be supplied alongside `state: CLOSED`. + - `spendingLimits`, when supplied, fully replaces the card's + application-enforced per-transaction limit. Supply one entry to set it or + an empty array to clear it. The entry's currency must match the card's + currency. Limits are supported only for card programs where Grid makes the + authorization decision. `spendingLimits` 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 @@ -163,6 +170,16 @@ patch: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + setSpendingLimit: + summary: Set the card's per-transaction spending limit + value: + spendingLimits: + - currencyCode: USD + maxPerTransaction: 10000 + clearSpendingLimit: + summary: Remove the card's per-transaction spending limit + value: + spendingLimits: [] freezeAndUpdateSources: summary: Freeze the card and replace its funding sources in one call value: diff --git a/openapi/webhooks/card-funding-source-change.yaml b/openapi/webhooks/card-funding-source-change.yaml index ff9174211..b0caea98f 100644 --- a/openapi/webhooks/card-funding-source-change.yaml +++ b/openapi/webhooks/card-funding-source-change.yaml @@ -60,6 +60,7 @@ post: fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:30:00Z' diff --git a/openapi/webhooks/card-state-change.yaml b/openapi/webhooks/card-state-change.yaml index c6d8caba0..50197e5d9 100644 --- a/openapi/webhooks/card-state-change.yaml +++ b/openapi/webhooks/card-state-change.yaml @@ -60,6 +60,9 @@ post: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD processorRef: card_b81c2a4f issuerRef: lead_card_7a1b9c3d @@ -79,6 +82,7 @@ post: form: VIRTUAL fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: [] currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-08T14:12:00Z' @@ -100,6 +104,9 @@ post: expYear: 2029 fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + spendingLimits: + - currencyCode: USD + maxPerTransaction: 5000 currency: USD createdAt: '2026-05-08T14:10:00Z' updatedAt: '2026-05-09T09:00:00Z'