From 26c3c02b31d67e5ac1729738fb988e5e33b2dcd7 Mon Sep 17 00:00:00 2001 From: Ben Goldberger Date: Mon, 6 Jul 2026 23:20:51 +0000 Subject: [PATCH] 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