Skip to content

feat(sca): add Strong Customer Authentication surface#558

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

feat(sca): add Strong Customer Authentication surface#558
jklein24 wants to merge 4 commits into
mainfrom
feat/striga-sca

Conversation

@jklein24

@jklein24 jklein24 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the Strong Customer Authentication (SCA) surface required for EU (Striga) customers, designed to stay invisible to every other caller. This is the grid-api half of the Striga SCA stack (sparkcore implementation lands separately).

What's added

  • New sca/ schemas: ScaFactor (SMS_OTP/TOTP/PASSKEY), ScaChallenge (id, expiresAt, factor, availableFactors, passkeyAssertionOptions), ScaAuthorization (code | passkeyAssertion).
  • scaChallenge (readOnly) on Quote and Transaction, present only while status is PENDING_AUTHORIZATION (new enum value on TransactionStatus and Quote.status); omitted entirely for providers that don't require SCA.
  • Resource-scoped authorize endpoints: POST /quotes/{quoteId}/authorizeQuote, POST /transactions/{transactionId}/authorizeTransaction. Both 409 for non-SCA providers.
  • Inline one-shot: optional scaAuthorization on execute (new ExecuteQuoteRequest) and transfer-out.
  • Realtime funding: create-quote returns 202 and withholds paymentInstructions until the challenge is authorized.

Invisibility guarantee

scaChallenge, PENDING_AUTHORIZATION, the 202, and the authorize endpoints only engage for customers whose provider requires SCA. Non-EU callers see no behavioral change.

Validation

  • npm run lint:openapi (redocly + spectral) passes; additions are additive/non-breaking.

Design

webdev docs/plans/2026-06-06-striga-sca-design.md

🤖 Generated with Claude Code

Adds the SCA surface required for EU (Striga) customers while keeping it
invisible to every other caller:

- New sca/ schemas: ScaFactor, ScaChallenge, ScaAuthorization.
- readOnly scaChallenge on Quote and Transaction, present only while status
  is PENDING_AUTHORIZATION (new enum value on TransactionStatus and
  Quote.status); omitted entirely for providers that don't require SCA.
- Resource-scoped authorize endpoints: POST /quotes/{quoteId}/authorize
  (returns Quote) and POST /transactions/{transactionId}/authorize (returns
  Transaction), plus an optional inline scaAuthorization on execute and
  transfer-out for one-shot completion.
- Realtime-funding create-quote returns 202 and withholds paymentInstructions
  until the challenge is authorized.

Design: webdev docs/plans/2026-06-06-striga-sca-design.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jun 8, 2026 7:26am

Request Review

@jklein24

jklein24 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

feat(api): add sca-authorization parameter to transfer-out, quotes execute methods

csharp

feat(api): add SCA support with scaChallenge fields, scaAuthorization params, status

go

feat(api): add SCA authorization params, scaChallenge fields, PENDING_AUTHORIZATION status

kotlin

feat(api): add scaChallenge/scaAuthorization for SCA to quotes/transactions

openapi

feat(api): add SCA authorize endpoints, scaChallenge/scaAuthorization to quotes/transactions

php

feat(api): add scaAuthorization param, scaChallenge field, PENDING_AUTHORIZATION status

python

feat(api): add sca_authorization param to quotes/transfers, sca_challenge to transaction types

ruby

feat(api): add sca_authorization param, sca_challenge to quotes/transactions/transfer_out

typescript

feat(api): add Strong Customer Authentication support to quotes/transfers

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-typescript studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅ (prev: build ❗) → lint ✅ (prev: lint ❗) → test ✅

npm install https://pkg.stainless.com/s/grid-typescript/fd0faabbb92355c257607c6b3a153201a606c3e5/dist.tar.gz
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-openapi studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅

New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-ruby studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅lint ✅test ✅

New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-go studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅lint ❗test ❗

go get github.com/stainless-sdks/grid-go@52e785c01a871deedb712d71ee4555acdf4d9945
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-kotlin studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅lint ✅test ✅

New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-python studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅lint ❗test ❗

pip install https://pkg.stainless.com/s/grid-python/4c49dbe7beb1aab2fbd40ef964e9af2a4bf50b03/grid-0.0.1-py3-none-any.whl
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-csharp studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️build ❗lint ✅test ❗

New diagnostics (5 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
💡 Name/Renamed: 264 names were renamed due to language constraints, so fallback names will be used instead.
grid-php studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅lint ✅test ✅

New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`
grid-cli studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️build ❗lint ❗test ❗

New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /quotes/{quoteId}/authorize`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /transactions/{transactionId}/authorize`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaAuthorization`
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/ScaChallenge`

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-06-08 07:32:46 UTC

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a Strong Customer Authentication (SCA) surface for EU/Striga customers, designed so that non-EU callers see no behavioral change. It adds three new schemas (ScaFactor, ScaChallenge, ScaAuthorization), two new authorize endpoints (POST /quotes/{quoteId}/authorize, POST /transactions/{transactionId}/authorize), an inline scaAuthorization field on execute and transfer-out, a new PENDING_AUTHORIZATION status on both Quote and Transaction, and a 202 response on create-quote for realtime-funding SCA flows.

  • New SCA schemas define the factor types (SMS_OTP/TOTP/PASSKEY), the server-issued challenge object, and the client-submitted authorization proof; all fields are well-described with sandbox hints.
  • Authorize endpoints follow the existing auth/404 convention and correctly document the 409 for non-SCA providers; the ScaChallenge description was updated to clarify that the challenge ID is informational.
  • Inline SCA on execute is clearly framed as upfront-only, with an explicit note that the only post-PENDING_AUTHORIZATION resolution is POST /transactions/{transactionId}/authorize.

Confidence Score: 5/5

Additive, non-breaking changes that are invisible to non-EU callers; safe to merge.

All changes are purely additive to the OpenAPI spec — new optional fields, new enum values, and new endpoints that return 409 for existing customers. No existing contract is modified. The SCA challenge flow is well-documented, and the authorize endpoints correctly mirror the conventions used elsewhere in the spec.

openapi/components/schemas/sca/ScaAuthorization.yaml warrants a second look for discriminated-union enforcement between the code and passkeyAssertion fields.

Important Files Changed

Filename Overview
openapi/components/schemas/sca/ScaAuthorization.yaml New schema describing the authorization proof; description says "exactly one of" code or passkeyAssertion, but no oneOf/anyOf discriminator is present in the schema — an empty body or both fields simultaneously are spec-valid.
openapi/components/schemas/sca/ScaChallenge.yaml New challenge schema; all required fields declared, id description correctly updated to clarify it is informational and not sent back, passkeyAssertionOptions correctly optional.
openapi/components/schemas/sca/ScaFactor.yaml New enum schema for SMS_OTP/TOTP/PASSKEY; includes useful per-factor notes including the TOTP dynamic-linking limitation.
openapi/components/schemas/transfers/TransferOutRequest.yaml Adds optional scaAuthorization field; "Omit it on the first call" phrasing could mislead developers into thinking the inline path involves re-calling transfer-out, when the correct resolution after PENDING_AUTHORIZATION is the separate authorize endpoint.
openapi/paths/quotes/quotes_{quoteId}_authorize.yaml New authorize endpoint for quotes; 400/401/404/409/500 responses correct, 409 usage for non-SCA providers documented, consistent with existing endpoint conventions.
openapi/paths/transactions/transactions_{transactionId}_authorize.yaml New authorize endpoint for transactions; returns TransactionOneOf on success, all response codes consistent with quote authorize endpoint.
openapi/paths/quotes/quotes_{quoteId}_execute.yaml Adds optional ExecuteQuoteRequest body; 200 description clearly documents upfront inline scaAuthorization vs. the PENDING_AUTHORIZATION/authorize-endpoint resolution path.
openapi/components/schemas/quotes/Quote.yaml Adds PENDING_AUTHORIZATION to the status enum and optional readOnly scaChallenge field; paymentInstructions remains optional (not required), correctly allowing it to be withheld in the 202 case.
openapi/components/schemas/transactions/TransactionStatus.yaml Adds PENDING_AUTHORIZATION value with clear SCA-scoped description and table row; consistent with other status values.
openapi/components/schemas/transactions/Transaction.yaml Adds optional readOnly scaChallenge field matching the pattern on Quote.yaml.
openapi/components/schemas/quotes/ExecuteQuoteRequest.yaml New optional request body schema for execute; scaAuthorization description clearly states upfront on the initial call without the misleading first call language found in TransferOutRequest.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant G as grid-api
    participant P as SCA Provider (Striga)

    rect rgb(240, 248, 255)
        Note over C,P: Realtime-funding quote flow (202)
        C->>G: POST /quotes
        G->>P: Create quote + SCA challenge
        P-->>G: Challenge issued (SMS sent)
        G-->>C: "202 Quote{status:PENDING_AUTHORIZATION, scaChallenge}"
        Note over C: Customer enters OTP
        C->>G: "POST /quotes/{quoteId}/authorize {code}"
        G->>P: Verify code
        P-->>G: OK
        G-->>C: "200 Quote{status:PENDING, paymentInstructions}"
    end

    rect rgb(255, 248, 240)
        Note over C,P: Execute (embedded-wallet) flow
        C->>G: "POST /quotes/{quoteId}/execute"
        G->>P: Execute + create SCA challenge
        P-->>G: Challenge issued (SMS sent)
        G-->>C: "200 Quote{status:PENDING_AUTHORIZATION, scaChallenge, transactionId}"
        Note over C: Customer enters OTP
        C->>G: "POST /transactions/{transactionId}/authorize {code}"
        G->>P: Verify code
        P-->>G: OK
        G-->>C: "200 Transaction{status:PROCESSING}"
    end

    rect rgb(240, 255, 248)
        Note over C,P: Transfer-out flow
        C->>G: POST /transfer-out
        G->>P: Create transaction + SCA challenge
        P-->>G: Challenge issued (SMS sent)
        G-->>C: "201 Transaction{status:PENDING_AUTHORIZATION, scaChallenge}"
        Note over C: Customer enters OTP
        C->>G: "POST /transactions/{transactionId}/authorize {code}"
        G->>P: Verify code
        P-->>G: OK
        G-->>C: "200 Transaction{status:PROCESSING}"
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
openapi/components/schemas/transfers/TransferOutRequest.yaml:22-27
The phrase "Omit it on the first call" implies there is a subsequent second call to `transfer-out` that uses the inline path — but after a `PENDING_AUTHORIZATION` response the resource is already created and re-calling `transfer-out` would create a duplicate transaction. The correct resolution path is `POST /transactions/{transactionId}/authorize`, not a second `transfer-out`. Compare the parallel field in `ExecuteQuoteRequest`, which uses "Omit it to receive…" without the "first call" framing and is unambiguous.

```suggestion
      Optional inline Strong Customer Authentication proof. Only relevant for
      customers whose provider requires SCA (e.g. EU): supply this to satisfy
      the challenge in the same call once the customer has the code/assertion.
      Omit it to receive the transaction in `PENDING_AUTHORIZATION` with an
      `scaChallenge`, then authorize it via
      `POST /transactions/{transactionId}/authorize`. Ignored for customers
      whose provider does not require SCA.
```

Reviews (4): Last reviewed commit: "docs(sca): document PENDING_AUTHORIZATIO..." | Re-trigger Greptile

Comment thread openapi/components/schemas/sca/ScaChallenge.yaml
Comment thread openapi/paths/quotes/quotes_{quoteId}_authorize.yaml
Address Greptile review: the authorize endpoints are resource-scoped, so the
server resolves the active challenge from the quote/transaction context. The id
is informational, not a field the client passes back in ScaAuthorization.

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

jklein24 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread openapi/paths/quotes/quotes_{quoteId}_execute.yaml Outdated
… only

Address Greptile review: after a quote returns PENDING_AUTHORIZATION, the only
resolution is POST /transactions/{transactionId}/authorize; re-calling execute
409s. scaAuthorization on ExecuteQuoteRequest applies only to the initial call
(to avoid the pending state entirely).

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

jklein24 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

Address Greptile review: mirror the execute/create-quote treatment so a
transfer-out client has a spec-level signal that the returned transaction may
be PENDING_AUTHORIZATION and require POST /transactions/{transactionId}/authorize.

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

jklein24 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review

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