Skip to content

docs(sca): document Strong Customer Authentication for EU customers#683

Draft
jklein24 wants to merge 18 commits into
mainfrom
feat/striga-sca-docs
Draft

docs(sca): document Strong Customer Authentication for EU customers#683
jklein24 wants to merge 18 commits into
mainfrom
feat/striga-sca-docs

Conversation

@jklein24

@jklein24 jklein24 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

A full, EU-scoped Strong Customer Authentication guide in the Mintlify docs. Stacks on #600 and documents the SCA surface added in #558/#600. What began as a single per-transaction snippet is now a dedicated guide section covering the whole SCA surface the API exposes, mirroring the structure of the reference docs it wraps (Striga's SCA docs).

What's added

A new "Strong Customer Authentication" group under the Get started tab (docs.json), with six pages under platform-overview/sca/:

  • overview — region scoping (EU / EUR / USDC), a "what SCA covers" table, factors + dynamic linking, a Mermaid flow diagram of the authorize loop, and lifetimes/limits + error tables.
  • per-transaction-authorization — reuses the existing snippets/sca/strong-customer-authentication.mdx as the single source (the three money-movement flow pages still import the same snippet, so they're unchanged): PENDING_AUTHORIZATION + scaChallenge, the status-driven authorize loop, inline vs. after-the-fact proof, resend, realtime-funding quotes, sandbox 123456.
  • factor-enrollment — TOTP + passkey enrollment (start/confirm), factor list/delete.
  • login-and-sessions — SCA login + the session it grants, plus account-security (record-event) signals and the lockout ladder.
  • trusted-beneficiaries — trust and untrust as start → confirm (keyed by externalAccountId).
  • two-factor-reset — liveness-gated start → poll → complete.

Review addressed

Incorporated the review on the guide:

  • Bounded the 2FA-reset poll loop (stop at expiresAt / client timeout; restart if the window closes before liveness passes).
  • Clarified the failed-login counter is cumulative and cleared only by RESET_PASSWORD_COMPLETED, not by a successful login.
  • Untrust is now documented as start → confirm — backed by the new startBeneficiaryUntrust endpoint added to feat(sca): SCA management surface — enrollment, login/session, beneficiary trust, 2FA reset #600 (previously SMS/passkey had no way to obtain the untrust challenge).
  • Fixed MD031/MD040 markdownlint violations on the new fenced code blocks.

Prose pass: em-dashes and AI-tell phrasing removed across the pages and the shared snippet.

Validation

  • markdownlint-cli2 passes clean on all SCA pages.
  • mint broken-links adds zero new broken links (pre-existing ones are unrelated and untouched).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jul 17, 2026 6:32pm
grid-wallet-demo Ignored Ignored Preview Jul 17, 2026 6:32pm

Request Review

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jklein24

Copy link
Copy Markdown
Contributor Author

@greptile review

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

⚔️ SCA OR FACE THE VOID ⚔️ \m/ (ಠ益ಠ) \m/

This PR documents Strong Customer Authentication for EU money movement. The main changes are:

  • Adds a reusable SCA guide covering factors, authorization loops, resend flows, and trusted beneficiaries.
  • Includes the guide in three relevant payment and conversion pages.
  • Guards realtime-funding instructions until SCA authorization completes.
  • Documents separate resend endpoints for transactions and quotes.

Confidence Score: 5/5

This looks safe to merge.

  • The realtime-funding example no longer reads withheld payment instructions.
  • The resend guide now includes the quote-scoped endpoint.
  • The shared snippet imports follow existing MDX conventions.
  • No blocking issues remain in the changed code.

Important Files Changed

Filename Overview
mintlify/snippets/sca/strong-customer-authentication.mdx Adds the reusable EU SCA guide and documents both quote- and transaction-scoped authorization flows.
mintlify/ramps/conversion-flows/fiat-crypto-conversion.mdx Adds the SCA guide and prevents access to withheld payment instructions while authorization is pending.
mintlify/payouts-and-b2b/payment-flow/send-payment.mdx Adds the shared SCA section to the payment flow.
mintlify/global-p2p/sending-receiving-payments/sending-payments.mdx Adds the shared SCA section to the global P2P payment flow.

Reviews (3): Last reviewed commit: "docs(sca): guard the fiat-to-crypto paym..." | Re-trigger Greptile

Comment thread mintlify/snippets/sca/strong-customer-authentication.mdx
Comment thread mintlify/snippets/sca/strong-customer-authentication.mdx Outdated
jklein24 added a commit that referenced this pull request Jul 13, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread mintlify/snippets/sca/strong-customer-authentication.mdx Outdated
jklein24 added a commit that referenced this pull request Jul 13, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24

Copy link
Copy Markdown
Contributor Author

@greptile review

@jklein24
jklein24 force-pushed the feat/striga-sca-management branch from af68ac7 to d71d44d Compare July 14, 2026 06:29
jklein24 added a commit that referenced this pull request Jul 14, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 14, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from b6d2de5 to 2280c96 Compare July 14, 2026 06:29
@jklein24
jklein24 force-pushed the feat/striga-sca-management branch from d71d44d to 7ddda22 Compare July 14, 2026 06:37
jklein24 added a commit that referenced this pull request Jul 14, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 14, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from 2280c96 to 36e9188 Compare July 14, 2026 06:37
jklein24 added a commit that referenced this pull request Jul 14, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 14, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch 2 times, most recently from d61ee03 to 0d87524 Compare July 14, 2026 08:23

@jklein24 jklein24 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

Comment thread mintlify/platform-overview/sca/trusted-beneficiaries.mdx Outdated
Comment thread mintlify/platform-overview/sca/two-factor-reset.mdx Outdated
Comment thread mintlify/platform-overview/sca/login-and-sessions.mdx
Comment thread mintlify/platform-overview/sca/login-and-sessions.mdx
jklein24 added a commit that referenced this pull request Jul 14, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 14, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from 78c7440 to a3a6b90 Compare July 14, 2026 17:20
Comment thread mintlify/platform-overview/sca/per-transaction-authorization.mdx
Comment thread mintlify/payouts-and-b2b/payment-flow/send-payment.mdx Outdated
jklein24 added a commit that referenced this pull request Jul 16, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from be5713d to 4ea44c2 Compare July 16, 2026 20:04
jklein24 and others added 8 commits July 17, 2026 10:21
Stacks on the per-transaction SCA spec (#558) and adds the remaining
Strong Customer Authentication surfaces an EU (Striga) partner needs, mirroring
the sparkcore handlers' exact routes + JSON shapes. All endpoints are EU-only
(409 for providers that don't require SCA).

- Factor enrollment: TOTP start/confirm, passkey register start/confirm,
  list factors, delete passkey.
- SCA login / 180-day session: login start/complete, plus record-event.
- Beneficiary trust: trust start (issues the SCA challenge + whitelistedId),
  trust/confirm and untrust/confirm (the whitelisting exemption that lets
  recurring payees skip per-transaction SCA).
- 2FA reset: initiate (201) -> poll status -> complete.

Reuses ScaFactor/ScaChallenge/ScaAuthorization from #558. Confirm/login bodies
carry the code|passkeyAssertion+origin proof; beneficiary confirm threads the
whitelistedId back so confirm never re-whitelists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Name the provider-reported terminal sentinels (LIVENESS_PASSED for 2FA
reset, SUCCESS for login) in the status field descriptions so consumers
can write polling/branching logic without out-of-band knowledge, while
keeping the field a verbatim provider passthrough rather than a locked
enum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mt schemas

Address faraday review on #600:
- Add an anyOf (code XOR passkeyAssertion+origin) to BeneficiaryTrustConfirmRequest
  and ScaLoginCompleteRequest so a proof-less body fails schema validation, not
  just at runtime (matching lint-ignore precedent for required-on-shared-props).
- Rename sumsubAccessToken -> livenessAccessToken (TwoFactorResetStart + reset
  start path) to keep the surface provider-neutral; the sparkcore response key
  is renamed in lockstep.
- Document whitelistedId provenance (scoped to this account's own trust start,
  not reusable across accounts or after expiry).
- Clarify eventType is a provider-defined closed vocabulary, not free-form input.

Co-Authored-By: Claude <noreply@anthropic.com>
Match the #558 terminology fix: the enrollment / login / trust / reset endpoints
now say "customers in a region where SCA is required (e.g. EU)" / "customers
outside SCA-regulated regions" instead of "customers whose payment provider
requires SCA", and the 409 reads "SCA is not required for this customer".
Incidental references to the underlying provider as a system (provider-reported
status, risk engine, whitelist handle) are left as-is.

Co-Authored-By: Claude <noreply@anthropic.com>
…nt surface

To the integrator, Grid is the provider; the underlying provider must never leak
into the docs. Reworded provider-reported status -> the (reported) status, the
provider's risk engine -> Grid's risk engine, whitelist/liveness handles and
'when the provider does not return one' -> neutral phrasing.

Co-Authored-By: Claude <noreply@anthropic.com>
Per review on #600:
- RecordSecurityEventRequest.eventType is now an explicit enum
  (RESET_PASSWORD_COMPLETED | FAILED_LOGIN_ATTEMPT) with each event's effect,
  instead of a vague free-string "closed vocabulary".
- Drop whitelistedId from the beneficiary-trust surface: BeneficiaryTrustStart
  returns just the scaChallenge, and confirm/untrust identify the beneficiary by
  the {externalAccountId} already in the path. Requires the backend to persist
  the externalAccountId -> whitelist handle mapping (tracked follow-up).

Co-Authored-By: Claude <noreply@anthropic.com>
Untrust previously had only a confirm step, so an SMS_OTP (default, no
enrollment) or PASSKEY customer had no way to obtain the SCA challenge that
untrust/confirm expects — only a self-served TOTP code worked. Add
POST /customers/{customerId}/external-accounts/{externalAccountId}/untrust
(startBeneficiaryUntrust), mirroring startBeneficiaryTrust: it issues the
scaChallenge that untrust/confirm satisfies. Returns BeneficiaryTrustStart.
…docs)

transfer-out has no associated quote and does not offer Strong Customer
Authentication — per-transaction SCA is authorized only on the quote
(POST /quotes/{quoteId}/authorize). Remove the vestigial scaFactor field from
TransferOutRequest and the SCA paragraph from the transfer-out 201 response
description, so the endpoint carries no SCA surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZsLXjVaJZbjjjoswBmhMu
jklein24 added a commit that referenced this pull request Jul 17, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 17, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-management branch from 94e5d2f to b7f7e5b Compare July 17, 2026 17:24
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from 4ea44c2 to 761678b Compare July 17, 2026 17:24
…llable

These fields are omitted (not sent as null) for TOTP/SMS_OTP factors, so
explicit null was the wrong contract. Drop 'null' from the type; the fields
stay optional via absence from `required`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015635zZx4bhUJHNBs5WdB4c
jklein24 added a commit that referenced this pull request Jul 17, 2026
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
jklein24 added a commit that referenced this pull request Jul 17, 2026
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from 761678b to 6bd7314 Compare July 17, 2026 18:03
jklein24 and others added 8 commits July 17, 2026 11:07
…lment

Expand the enrollment description to explain that SMS_OTP is not enrolled
through POST /sca/factors — every customer in an SCA-regulated region has a
verified phone from customer creation (Contact Verification flows), so SMS is
always available as a factor. TOTP/PASSKEY remain the explicit opt-in factors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015635zZx4bhUJHNBs5WdB4c
Add an EU-scoped SCA section to the money-movement guides where it's relevant —
payouts send-payment, global-p2p sending-payments, and ramps fiat/crypto
conversion — via a reusable snippet. Mirrors Striga's SCA docs, adapted to
Grid's surface: when a transaction/quote comes back PENDING_AUTHORIZATION with
an scaChallenge, satisfy it through the authorize endpoints in a status-driven
loop (may be more than one), covering the SMS/passkey factors, inline
scaAuthorization, resend, sandbox 123456, and trusted-beneficiary. Scoped so
non-EU integrators have nothing to do.

Co-Authored-By: Claude <noreply@anthropic.com>
Address Greptile review on #683:
- Document the quote resend endpoint (POST /quotes/{quoteId}/authorize/resend)
  alongside the transaction one, since a realtime-funding quote's SMS challenge
  exists before any transaction.
- Note that a realtime-funding 202 withholds paymentInstructions until the
  challenge is authorized, so integrators authorize first, then read them.

Co-Authored-By: Claude <noreply@anthropic.com>
…CA-pending quotes

The runnable displayPaymentInstructions example dereferenced
quote.paymentInstructions[0] unconditionally, which throws for an EU
realtime-funding quote returned as PENDING_AUTHORIZATION (instructions withheld
until the scaChallenge is authorized). Guard for the pending case and point at
the SCA section. Addresses Greptile P1 on #683.

Co-Authored-By: Claude <noreply@anthropic.com>
Match the #558/#600 terminology fix in the embedded SCA docs section: 'customers
in a region where SCA is required (EU)' / 'customers outside SCA-regulated
regions' instead of 'whose payment provider requires SCA'.

Co-Authored-By: Claude <noreply@anthropic.com>
Grows the SCA docs from a single per-transaction snippet into a dedicated
"Strong Customer Authentication" guide covering the whole EU surface the API
exposes, mirroring the structure of the reference docs it wraps.

- overview: region scope, a "what SCA covers" coverage table, factors +
  dynamic-linking, a Mermaid flow diagram, lifetimes/limits and error tables,
  and navigation cards.
- per-transaction-authorization: reuses the existing snippet as the single
  source (flow pages unchanged).
- factor-enrollment: TOTP + passkey enrollment, list, delete.
- login-and-sessions: SCA login + the session it grants, plus account-security
  (record-event) signals and the lockout ladder.
- trusted-beneficiaries: start / confirm / untrust walkthrough (keyed by
  externalAccountId).
- two-factor-reset: liveness-gated reset (start -> poll -> complete).

All EU-scoped; every endpoint 409s for non-SCA customers.
- two-factor-reset: give the poll loop a bounded stop condition (stop at
  expiresAt / a client timeout; start a new reset if the window closes before
  liveness passes) instead of implying an open-ended wait.
- login-and-sessions: note the failed-login counter is cumulative and cleared
  only by RESET_PASSWORD_COMPLETED, not by a successful login.
- trusted-beneficiaries: untrust has no start step, so document that it's
  confirmed with a self-produced proof (TOTP) and challengeId is omitted;
  SMS/passkey untrust would need a start endpoint that isn't in the surface yet.
- fix MD031/MD040 markdownlint violations on the new fenced code blocks.
Now that startBeneficiaryUntrust exists, untrust mirrors trust (start issues the
challenge, confirm submits the proof), so drop the earlier note about untrust
having no start step.
@jklein24
jklein24 force-pushed the feat/striga-sca-docs branch from 6bd7314 to 332b681 Compare July 17, 2026 18:07
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

No changes were made to the SDKs.


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-17 18:32:13 UTC

The transfer-out SCA/deprecation language was removed from the endpoint spec in
#600, so drop the "being deprecated" claim from the per-transaction guide and
stop showing transfer-out as an SCA-challenge-producing path in the overview
sequence diagram. transfer-out has no associated quote and offers no SCA-gated
EU debits; the guide points EU callers to the quote + execute flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZsLXjVaJZbjjjoswBmhMu
Base automatically changed from feat/striga-sca-management to main July 17, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant