docs(sca): document Strong Customer Authentication for EU customers#683
docs(sca): document Strong Customer Authentication for EU customers#683jklein24 wants to merge 18 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
@greptile review |
Greptile Summary⚔️ SCA OR FACE THE VOID ⚔️ This PR documents Strong Customer Authentication for EU money movement. The main changes are:
Confidence Score: 5/5This looks safe to merge.
|
| 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
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>
|
@greptile review |
…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>
|
@greptile review |
af68ac7 to
d71d44d
Compare
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>
b6d2de5 to
2280c96
Compare
d71d44d to
7ddda22
Compare
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>
2280c96 to
36e9188
Compare
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>
d61ee03 to
0d87524
Compare
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>
78c7440 to
a3a6b90
Compare
…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>
be5713d to
4ea44c2
Compare
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
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>
94e5d2f to
b7f7e5b
Compare
4ea44c2 to
761678b
Compare
…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
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>
761678b to
6bd7314
Compare
…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>
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.
6bd7314 to
332b681
Compare
✱ Stainless preview builds for gridNo changes were made to the SDKs. This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push. |
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

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 underplatform-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 existingsnippets/sca/strong-customer-authentication.mdxas 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, sandbox123456.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 byexternalAccountId).two-factor-reset— liveness-gated start → poll → complete.Review addressed
Incorporated the review on the guide:
expiresAt/ client timeout; restart if the window closes before liveness passes).RESET_PASSWORD_COMPLETED, not by a successful login.startBeneficiaryUntrustendpoint 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).Prose pass: em-dashes and AI-tell phrasing removed across the pages and the shared snippet.
Validation
markdownlint-cli2passes clean on all SCA pages.mint broken-linksadds zero new broken links (pre-existing ones are unrelated and untouched).🤖 Generated with Claude Code