From 26c3c02b31d67e5ac1729738fb988e5e33b2dcd7 Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Mon, 6 Jul 2026 23:20:51 +0000 Subject: [PATCH 1/6] docs(cards): panEmbedUrl is nullable and minted only on GET /cards/{id} Make Card.panEmbedUrl nullable in the OpenAPI schema and document the fetch-on-demand reveal pattern: the signed URL is populated only on the single-card GET, is short-lived, and must be fetched right before rendering the PAN-reveal iframe. Update the issuing-cards, quickstart, webhooks, and terminology docs (and the CARD.STATE_CHANGE webhook example) that previously showed panEmbedUrl arriving populated in the webhook payload. Follow-up #2 from webdev#29877. Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 10 +++++---- mintlify/snippets/cards/issuing-cards.mdx | 25 +++++++++++++++++----- mintlify/snippets/cards/quickstart.mdx | 10 +++++---- mintlify/snippets/cards/terminology.mdx | 4 +++- mintlify/snippets/cards/webhooks.mdx | 8 ++++--- openapi.yaml | 10 +++++---- openapi/components/schemas/cards/Card.yaml | 15 ++++++++----- openapi/paths/cards/cards_{id}.yaml | 6 +++++- openapi/webhooks/card-state-change.yaml | 2 +- 9 files changed, 62 insertions(+), 28 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 9beb0ec97..603c1517d 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -7030,7 +7030,7 @@ paths: type: string get: summary: Get a card - description: Retrieve a card by its system-generated id. + description: Retrieve a card by its system-generated id. This is the only endpoint that returns a populated `panEmbedUrl` — a fresh, short-lived signed URL is minted on each call. Fetch the card right before rendering the PAN-reveal iframe. operationId: getCardById tags: - Cards @@ -8566,7 +8566,7 @@ webhooks: last4: '4242' expMonth: 12 expYear: 2029 - panEmbedUrl: https://embed.lithic.com/iframe/...?t=... + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD @@ -19693,9 +19693,11 @@ components: description: Card expiration year (four digits). example: 2029 panEmbedUrl: - type: string + type: + - string + - 'null' format: uri - description: 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. + 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. Populated ONLY on `GET /cards/{id}`; null on list, create, and update responses and in webhook payloads. The URL is short-lived (expires within minutes), so fetch the card right before rendering the iframe and never cache the URL. example: https://embed.lithic.com/iframe/...?t=... fundingSources: type: array diff --git a/mintlify/snippets/cards/issuing-cards.mdx b/mintlify/snippets/cards/issuing-cards.mdx index f0c36bbd5..aaf8f335e 100644 --- a/mintlify/snippets/cards/issuing-cards.mdx +++ b/mintlify/snippets/cards/issuing-cards.mdx @@ -54,17 +54,32 @@ issuance is gated on KYC up front. `POST /cards` returns immediately with `state: "PROCESSING"`. The issuer provisions the card asynchronously; on success a `CARD.STATE_CHANGE` webhook fires with the activated `Card` resource -including the populated `last4`, `expMonth`, `expYear`, and -`panEmbedUrl`. +including the populated `last4`, `expMonth`, and `expYear`. If the issuer rejects provisioning, the same webhook fires with `state: "CLOSED"` and `stateReason: "ISSUER_REJECTED"`. That card is terminal — issue a new one with a fresh `platformCardId` to retry. +## Revealing the PAN + +To show the cardholder their full PAN, CVV, and expiry, fetch the +single card right before rendering: + +```bash +curl -X GET "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010" \ + -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" +``` + +The response includes a `panEmbedUrl` — a signed URL for the card +processor's iframe that renders the full credentials directly to the +cardholder. The full PAN and CVV never cross your servers or Grid's. + -Render `panEmbedUrl` in an iframe in your client to display the full -PAN, CVV, and expiry to the cardholder. The full credentials never -cross your servers. +`panEmbedUrl` is populated **only** on `GET /cards/{id}` — it is null +on list, create, and update responses and in webhook payloads. The URL +is short-lived (it expires within minutes), so fetch the card each time +the cardholder asks to reveal their details, immediately before +rendering the iframe. Don't cache or store the URL. ## Errors to handle diff --git a/mintlify/snippets/cards/quickstart.mdx b/mintlify/snippets/cards/quickstart.mdx index 459fdc215..5fbd6f344 100644 --- a/mintlify/snippets/cards/quickstart.mdx +++ b/mintlify/snippets/cards/quickstart.mdx @@ -70,7 +70,7 @@ for the full table. When activation completes, a "last4": "4242", "expMonth": 12, "expYear": 2029, - "panEmbedUrl": "https://embed.lithic.com/iframe/...?t=...", + "panEmbedUrl": null, "fundingSources": [ "InternalAccount:019542f5-b3e7-1d02-0000-000000000002" ], @@ -84,9 +84,11 @@ for the full table. When activation completes, a ``` -Render `panEmbedUrl` in an iframe in your client to display the full -PAN, CVV, and expiry to the cardholder. The full card credentials never -cross your servers. +To display the full PAN, CVV, and expiry to the cardholder, fetch +`GET /cards/{id}` and render the returned `panEmbedUrl` in an iframe. +The URL is short-lived and populated only on the single-card GET (it's +null in webhook payloads), so fetch it right before rendering. The full +card credentials never cross your servers. ## Simulate an authorization diff --git a/mintlify/snippets/cards/terminology.mdx b/mintlify/snippets/cards/terminology.mdx index db8888326..eeb73669f 100644 --- a/mintlify/snippets/cards/terminology.mdx +++ b/mintlify/snippets/cards/terminology.mdx @@ -60,4 +60,6 @@ high-urgency reconciliation alerts and are surfaced as `panEmbedUrl` on the `Card` resource is the issuer's iframe URL that renders the full PAN, CVV, and expiry directly to the cardholder. Render it in an iframe in your client; the full credentials never cross -{` `}Grid's servers. +{` `}Grid's servers. The URL is short-lived and populated only on +`GET /cards/{id}` — fetch the card right before rendering the iframe +rather than caching the URL. diff --git a/mintlify/snippets/cards/webhooks.mdx b/mintlify/snippets/cards/webhooks.mdx index da0f8cd01..2b5340ad2 100644 --- a/mintlify/snippets/cards/webhooks.mdx +++ b/mintlify/snippets/cards/webhooks.mdx @@ -47,7 +47,7 @@ activation after issuance: "last4": "4242", "expMonth": 12, "expYear": 2029, - "panEmbedUrl": "https://embed.lithic.com/iframe/...?t=...", + "panEmbedUrl": null, "fundingSources": [ "InternalAccount:019542f5-b3e7-1d02-0000-000000000002" ], @@ -62,8 +62,10 @@ activation after issuance: Common branches to handle in your consumer: -- `state: "ACTIVE"` after `PROCESSING` — the card is live; surface - `panEmbedUrl` to the cardholder. +- `state: "ACTIVE"` after `PROCESSING` — the card is live. To reveal + the full card details, fetch `GET /cards/{id}` right before rendering + its short-lived `panEmbedUrl` in an iframe — the field is always null + in webhook payloads. - `state: "CLOSED"`, `stateReason: "ISSUER_REJECTED"` — the issuer rejected provisioning; offer to issue a new card. - `state: "FROZEN"` / `state: "ACTIVE"` — reflect the freeze toggle in diff --git a/openapi.yaml b/openapi.yaml index 9beb0ec97..603c1517d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -7030,7 +7030,7 @@ paths: type: string get: summary: Get a card - description: Retrieve a card by its system-generated id. + description: Retrieve a card by its system-generated id. This is the only endpoint that returns a populated `panEmbedUrl` — a fresh, short-lived signed URL is minted on each call. Fetch the card right before rendering the PAN-reveal iframe. operationId: getCardById tags: - Cards @@ -8566,7 +8566,7 @@ webhooks: last4: '4242' expMonth: 12 expYear: 2029 - panEmbedUrl: https://embed.lithic.com/iframe/...?t=... + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD @@ -19693,9 +19693,11 @@ components: description: Card expiration year (four digits). example: 2029 panEmbedUrl: - type: string + type: + - string + - 'null' format: uri - description: 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. + 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. Populated ONLY on `GET /cards/{id}`; null on list, create, and update responses and in webhook payloads. The URL is short-lived (expires within minutes), so fetch the card right before rendering the iframe and never cache the URL. example: https://embed.lithic.com/iframe/...?t=... fundingSources: type: array diff --git a/openapi/components/schemas/cards/Card.yaml b/openapi/components/schemas/cards/Card.yaml index 10ac4acad..5f77387e6 100644 --- a/openapi/components/schemas/cards/Card.yaml +++ b/openapi/components/schemas/cards/Card.yaml @@ -51,13 +51,18 @@ properties: description: Card expiration year (four digits). example: 2029 panEmbedUrl: - type: string + type: + - string + - 'null' format: uri description: >- - 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. + 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. Populated ONLY on `GET /cards/{id}`; null on + list, create, and update responses and in webhook payloads. The URL + is short-lived (expires within minutes), so fetch the card right + before rendering the iframe and never cache the URL. example: https://embed.lithic.com/iframe/...?t=... fundingSources: type: array diff --git a/openapi/paths/cards/cards_{id}.yaml b/openapi/paths/cards/cards_{id}.yaml index 4aa751038..ff74cd026 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -7,7 +7,11 @@ parameters: type: string get: summary: Get a card - description: Retrieve a card by its system-generated id. + description: >- + Retrieve a card by its system-generated id. This is the only endpoint + that returns a populated `panEmbedUrl` — a fresh, short-lived signed + URL is minted on each call. Fetch the card right before rendering the + PAN-reveal iframe. operationId: getCardById tags: - Cards diff --git a/openapi/webhooks/card-state-change.yaml b/openapi/webhooks/card-state-change.yaml index da96c3bb1..ef20bac32 100644 --- a/openapi/webhooks/card-state-change.yaml +++ b/openapi/webhooks/card-state-change.yaml @@ -58,7 +58,7 @@ post: last4: '4242' expMonth: 12 expYear: 2029 - panEmbedUrl: https://embed.lithic.com/iframe/...?t=... + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD From d573002db0a86bfb5a875ad16ab61366871dec3b Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Tue, 7 Jul 2026 01:05:42 +0000 Subject: [PATCH 2/6] Show explicit panEmbedUrl: null in all card-state-change webhook examples The frozen and issuerRejected examples omitted the field while activated showed it as null; the serializer always emits the key, so all three now match the wire format. Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 2 ++ openapi.yaml | 2 ++ openapi/webhooks/card-state-change.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 603c1517d..72213a101 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8586,6 +8586,7 @@ webhooks: state: CLOSED stateReason: ISSUER_REJECTED form: VIRTUAL + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD @@ -8607,6 +8608,7 @@ webhooks: last4: '4242' expMonth: 12 expYear: 2029 + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD diff --git a/openapi.yaml b/openapi.yaml index 603c1517d..72213a101 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8586,6 +8586,7 @@ webhooks: state: CLOSED stateReason: ISSUER_REJECTED form: VIRTUAL + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD @@ -8607,6 +8608,7 @@ webhooks: last4: '4242' expMonth: 12 expYear: 2029 + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD diff --git a/openapi/webhooks/card-state-change.yaml b/openapi/webhooks/card-state-change.yaml index ef20bac32..679fd812a 100644 --- a/openapi/webhooks/card-state-change.yaml +++ b/openapi/webhooks/card-state-change.yaml @@ -78,6 +78,7 @@ post: state: CLOSED stateReason: ISSUER_REJECTED form: VIRTUAL + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD @@ -99,6 +100,7 @@ post: last4: '4242' expMonth: 12 expYear: 2029 + panEmbedUrl: null fundingSources: - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 currency: USD From 0bc4ba1b0120deeec6d9b7fff8c495cc7d875807 Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Tue, 7 Jul 2026 01:17:45 +0000 Subject: [PATCH 3/6] Skip the broken Stainless csharp SDK target so spec PRs stop failing CI Folded in from #656 so this PR's preview check goes green on its own. Co-Authored-By: mohamedwane Co-Authored-By: benwgold --- .stainless/stainless.yml | 3 +++ mintlify/openapi.yaml | 2 +- openapi.yaml | 2 +- openapi/components/schemas/common/GtqAccountInfoBase.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 85db268ed..e94db9af3 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -58,6 +58,9 @@ targets: package_name: grid production_repo: null csharp: + # Skipped: Stainless's csharp build CI fails on any freshly generated code, + # failing the SDK preview check on every spec-changing PR. Unpublished target. + skip: true edition: csharp.2025-10-08 package_name: grid production_repo: null diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 72213a101..1290b4ebb 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -12323,7 +12323,7 @@ components: - SAVINGS bankName: type: string - description: The name of the bank + description: The name of the beneficiary's bank minLength: 1 maxLength: 255 example: Banco GYT Continental diff --git a/openapi.yaml b/openapi.yaml index 72213a101..1290b4ebb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -12323,7 +12323,7 @@ components: - SAVINGS bankName: type: string - description: The name of the bank + description: The name of the beneficiary's bank minLength: 1 maxLength: 255 example: Banco GYT Continental diff --git a/openapi/components/schemas/common/GtqAccountInfoBase.yaml b/openapi/components/schemas/common/GtqAccountInfoBase.yaml index 8cf8e1be6..250c99250 100644 --- a/openapi/components/schemas/common/GtqAccountInfoBase.yaml +++ b/openapi/components/schemas/common/GtqAccountInfoBase.yaml @@ -22,7 +22,7 @@ properties: - SAVINGS bankName: type: string - description: The name of the bank + description: The name of the beneficiary's bank minLength: 1 maxLength: 255 example: Banco GYT Continental From 6d417299b02eab24a20e3b390c933011a1a151f0 Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Tue, 7 Jul 2026 01:26:21 +0000 Subject: [PATCH 4/6] Retrigger Stainless preview on the folded-in csharp skip Co-Authored-By: Claude Fable 5 From 069ad9a3a47c6ab3c0370a28daa61d57505532af Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Tue, 7 Jul 2026 01:28:32 +0000 Subject: [PATCH 5/6] Remove the csharp Stainless target instead of skipping it skip: true doesn't stop the target's build CI job from gating the preview check (656's green runs passed only because their base build had no cached csharp success to regress from). With no csharp target in the config there is no head build outcome to compare, so the broken upstream csharp build CI can't fail the check. Co-Authored-By: benwgold --- .stainless/stainless.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index e94db9af3..3fd2b138e 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -57,15 +57,8 @@ targets: edition: go.2026-04-15 package_name: grid production_repo: null - csharp: - # Skipped: Stainless's csharp build CI fails on any freshly generated code, - # failing the SDK preview check on every spec-changing PR. Unpublished target. - skip: true - edition: csharp.2025-10-08 - package_name: grid - production_repo: null - publish: - nuget: false + # csharp (unpublished) is omitted: its Stainless build CI fails on any + # freshly generated code, failing the preview check on spec-changing PRs. php: edition: php.2025-10-08 package_name: grid From 371594fc07e2b7cb1b0ac28f333050f63e956041 Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Tue, 7 Jul 2026 02:17:47 +0000 Subject: [PATCH 6/6] Restore the csharp Stainless target Keep csharp configured; the preview check stays red on codegen-delta PRs until Stainless fixes their csharp build CI upstream. Co-Authored-By: benwgold --- .stainless/stainless.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 3fd2b138e..85db268ed 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -57,8 +57,12 @@ targets: edition: go.2026-04-15 package_name: grid production_repo: null - # csharp (unpublished) is omitted: its Stainless build CI fails on any - # freshly generated code, failing the preview check on spec-changing PRs. + csharp: + edition: csharp.2025-10-08 + package_name: grid + production_repo: null + publish: + nuget: false php: edition: php.2025-10-08 package_name: grid