Skip to content
478 changes: 447 additions & 31 deletions mintlify/openapi.yaml

Large diffs are not rendered by default.

478 changes: 447 additions & 31 deletions openapi.yaml

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions openapi/components/schemas/quotes/ExecuteQuoteRequest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type: object
description: >-
Optional body for executing a quote. Only needed to supply an inline Strong
Customer Authentication proof; omit the body entirely for quotes that do not
require SCA.
properties:
scaFactor:
$ref: ../sca/ScaFactor.yaml
description: >-
Optional preferred factor for the Strong Customer Authentication challenge
this call issues. Only relevant for customers whose provider requires SCA
(e.g. EU); ignored otherwise. Valid values for a per-transaction challenge
are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required
dynamic linking and is rejected here. Omit to default to `SMS_OTP`.
scaAuthorization:
$ref: ../sca/ScaAuthorization.yaml
description: >-
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 quote in `PENDING_AUTHORIZATION` with an
`scaChallenge`, then authorize separately. Ignored for customers whose
provider does not require SCA.
16 changes: 15 additions & 1 deletion openapi/components/schemas/quotes/Quote.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ properties:
type: string
enum:
- PENDING
- PENDING_AUTHORIZATION

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We're adding PENDING_AUTHORIZATION to a closed-vocabulary enum on endpoints that are already shipped (same issue applies to TransactionStatus and OutgoingTransactionStatus). The generated SDKs model these as closed enums with no unknown-value fallback - e.g. the Python client's status_validate_enum raises ValueError on anything not in the list. The moment sparkcore emits this status for an SCA-required customer, any integrator still on an older SDK gets a hard validation error on a previously-working GET /transactions/{id} or GET /quotes/{id} call, and this will likely repeat across every generated language (go/kotlin/ts/rust/flutter). This needs version gating per grid.md §8 rather than extending the existing enum in place.

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.

It's fine. Our real SDKs handle new values gracefully.

- PROCESSING
- COMPLETED
- FAILED
- EXPIRED
description: Current status of the quote
description: >-
Current status of the quote. `PENDING_AUTHORIZATION` occurs only for
customers whose provider requires Strong Customer Authentication (e.g.
EU): the quote carries an `scaChallenge` that must be authorized before
execution, and for realtime-funding sources `paymentInstructions` are
withheld until it is satisfied.
example: PENDING
createdAt:
type: string
Expand Down Expand Up @@ -120,3 +126,11 @@ properties:
rateDetails:
$ref: ../transactions/OutgoingRateDetails.yaml
description: Details about the rate and fees for the transaction.
scaChallenge:
$ref: ../sca/ScaChallenge.yaml
readOnly: true
description: >-
Present only while `status` is `PENDING_AUTHORIZATION`: the Strong
Customer Authentication challenge to satisfy before this quote can be
executed (or, for realtime-funding sources, before `paymentInstructions`
are issued). Omitted for customers whose provider does not require SCA.
9 changes: 9 additions & 0 deletions openapi/components/schemas/quotes/QuoteRequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ properties:
example: 'Invoice #1234 payment'
purposeOfPayment:
$ref: ./PurposeOfPayment.yaml
scaFactor:
$ref: ../sca/ScaFactor.yaml
description: >-
Optional preferred factor for a Strong Customer Authentication challenge
issued at quote creation. Only relevant for a realtime-funding source
whose provider requires SCA (e.g. EU); ignored otherwise. Valid values are
`SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required
dynamic linking and is rejected. When the quote is returned in
`PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`.
senderCustomerInfo:
type: object
additionalProperties: true
Expand Down
37 changes: 37 additions & 0 deletions openapi/components/schemas/sca/ScaAuthorization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
type: object
description: >-
Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for
`SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a
`passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound
to the origin it was produced against, and the provider rejects a passkey
confirmation without it.
properties:
code:
type:
- string
- 'null'
description: >-
The one-time code the customer received by SMS, or read from their
authenticator app. In sandbox, the code is always `123456`.
example: '123456'
passkeyAssertion:
type:
- object
- 'null'
additionalProperties: true
description: >-
Opaque WebAuthn assertion produced by the device from the challenge's
`passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge.
origin:
type:
- string
- 'null'
description: >-
The WebAuthn origin the `passkeyAssertion` was produced against.
**Required** alongside `passkeyAssertion`; omit it for the `code` path.
When the challenge lists `passkeyAllowedOrigins` (enrollment / login
challenges), it must be one of those. A per-transaction passkey challenge
carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in
that case supply the origin your app invoked the WebAuthn API from, which
must match the relying party in `passkeyAssertionOptions`.
example: https://app.example.com
67 changes: 67 additions & 0 deletions openapi/components/schemas/sca/ScaChallenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
type: object
description: >-
A Strong Customer Authentication challenge that must be satisfied before a
money-movement operation can complete. This object is **only present when the
customer's payment provider requires SCA** (e.g. EU customers on an e-money
provider); for customers whose provider has no such requirement it is omitted
entirely and no action is needed.


When present on a quote or transaction, authorize it by submitting an
`ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize` or
`POST /transactions/{transactionId}/authorize`, or inline via the optional
`scaAuthorization` field on the originating `execute` / `transfer-out` call.
required:
- id
- expiresAt
- factor
- availableFactors
properties:
id:
type: string
description: >-
Unique identifier for this challenge. The server resolves the active
challenge from the quote or transaction being authorized, so this field
need not be supplied back; it is informational (e.g. for logging or
correlation).
example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007
Comment thread
jklein24 marked this conversation as resolved.
expiresAt:
type: string
format: date-time
description: Absolute UTC timestamp after which this challenge can no longer be authorized.
example: '2025-10-03T12:05:00Z'
factor:
$ref: ./ScaFactor.yaml
description: The factor this challenge was issued for. Defaults to `SMS_OTP`.
availableFactors:
type: array
description: The factors the customer may use to satisfy this challenge.
items:
$ref: ./ScaFactor.yaml
example:
- SMS_OTP
passkeyAssertionOptions:
type:
- object
- 'null'
additionalProperties: true
description: >-
Opaque WebAuthn assertion request options (including the relying-party id,
challenge, and allowed credentials), present only when `factor` is
`PASSKEY`. Pass to the device's WebAuthn API to produce the assertion
submitted back in `ScaAuthorization.passkeyAssertion`.
passkeyAllowedOrigins:
type:
- array
- 'null'
items:
type: string
description: >-
The origins the WebAuthn ceremony may run against. Populated for
enrollment and login passkey challenges; the origin the assertion is
produced against must be one of these and echoed back as
`ScaAuthorization.origin`. Per-transaction passkey challenges omit this
(they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin`
for how to source the origin in that case.
example:
- https://app.example.com
13 changes: 13 additions & 0 deletions openapi/components/schemas/sca/ScaFactor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: string
enum:
- SMS_OTP
- TOTP
- PASSKEY
description: |
A Strong Customer Authentication factor.

| Factor | Description |
|--------|-------------|
| `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. |
| `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). |
| `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. |
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type: string
enum:
- PENDING
- PENDING_AUTHORIZATION
- EXPIRED
- PROCESSING
- COMPLETED
Expand All @@ -11,6 +12,7 @@ description: |
| Status | Description |
|--------|-------------|
| `PENDING` | Quote is pending confirmation |
| `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers whose provider requires SCA (e.g. EU); authorize the transaction's `scaChallenge` to proceed. |
| `EXPIRED` | Quote wasn't executed before expiry window |
| `PROCESSING` | Executing the quote after receiving funds |
| `COMPLETED` | Payout successfully reached the destination |
Expand Down
8 changes: 8 additions & 0 deletions openapi/components/schemas/transactions/Transaction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,11 @@ properties:
example: 'Payment for invoice #1234'
counterpartyInformation:
$ref: ./CounterpartyInformation.yaml
scaChallenge:
$ref: ../sca/ScaChallenge.yaml
readOnly: true
description: >-
Present only while `status` is `PENDING_AUTHORIZATION`: the Strong
Customer Authentication challenge to satisfy before this transaction can
proceed. Omitted entirely for customers whose provider does not require
SCA.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type: string
enum:
- CREATED
- PENDING
- PENDING_AUTHORIZATION
- PROCESSING
- COMPLETED
- REJECTED
Expand All @@ -15,6 +16,7 @@ description: |
|--------|-------------|
| `CREATED` | Initial lookup has been created |
| `PENDING` | Quote has been created |
| `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers whose provider requires SCA (e.g. EU); authorize the transaction's `scaChallenge` to proceed. |
| `PROCESSING` | Funding has been received and payment initiated |
| `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network |
| `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded |
Expand Down
17 changes: 17 additions & 0 deletions openapi/components/schemas/transfers/TransferOutRequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,20 @@ properties:
remittanceInformation field, and for wires it populates the OBI
(Originator to Beneficiary Information) / beneficiary information.
example: '12345'
scaFactor:
$ref: ../sca/ScaFactor.yaml
description: >-
Optional preferred factor for the Strong Customer Authentication challenge
this call issues. Only relevant for customers whose provider requires SCA
(e.g. EU); ignored otherwise. Valid values for a per-transaction challenge
are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required
dynamic linking and is rejected here. Omit to default to `SMS_OTP`.
scaAuthorization:
$ref: ../sca/ScaAuthorization.yaml
description: >-
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 on the first call to receive the transaction in
`PENDING_AUTHORIZATION` with an `scaChallenge`, then authorize separately.
Ignored for customers whose provider does not require SCA.
14 changes: 14 additions & 0 deletions openapi/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions openapi/paths/quotes/quotes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ post:
exchangeRate: 0.92
feesIncluded: 10
transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005
'202':

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Adding a bare 202 to this existing operation is going to silently break already-deployed SDK clients. I checked the generated Python client - _response_types_map only has an entry for 201 (no 202, no 2XX wildcard), so response_deserialize falls through with response_type=None and create_quote() returns None even though a quote was actually created. That's silent data loss on a money-creation call, not something the caller can catch. Per grid.md §8 a breaking change like this to an existing endpoint needs a new dated blueprint rather than extending this one in place.

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.

It's fine. I'm not worried about that generated python client. We'll update it before this is actually returned.

Comment thread
jklein24 marked this conversation as resolved.
description: >
Quote created but awaiting Strong Customer Authentication. Returned only
for customers whose provider requires SCA (e.g. EU) on a realtime-funding
source: the quote has status `PENDING_AUTHORIZATION` and carries an
`scaChallenge`, and `paymentInstructions` are **withheld** until the
challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once
authorized, the quote's `paymentInstructions` are populated.
content:
application/json:
schema:
$ref: ../../components/schemas/quotes/Quote.yaml
'400':
description: Bad request - Missing or invalid parameters
content:
Expand Down
85 changes: 85 additions & 0 deletions openapi/paths/quotes/quotes_{quoteId}_authorize.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
parameters:
- name: quoteId
in: path
description: The unique identifier of the quote whose SCA challenge is being authorized.
required: true
schema:
type: string
example: Quote:019542f5-b3e7-1d02-0000-000000000006
post:
summary: Authorize a quote's SCA challenge
description: |
Satisfy the Strong Customer Authentication challenge carried by a quote in
`PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof.

This is used for realtime-funding quotes: the quote is returned with an
`scaChallenge` and **without** `paymentInstructions`; once authorized, the
quote advances and its `paymentInstructions` are populated so the customer
can fund the transfer.

This endpoint is only meaningful for customers whose payment provider
requires SCA (e.g. EU customers). For customers whose provider has no such
requirement, this returns `409`.

In sandbox, the SMS code is always `123456`.
operationId: authorizeQuote
tags:
- Strong Customer Authentication
security:
- BasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: ../../components/schemas/sca/ScaAuthorization.yaml
responses:
'200':
description: Challenge authorized; the updated quote is returned.
content:
application/json:
schema:
$ref: ../../components/schemas/quotes/Quote.yaml
'400':
description: Invalid or expired authorization proof
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error400.yaml
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error401.yaml
'404':
description: Quote not found
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error404.yaml
'409':
description: >-
The customer's payment provider does not require SCA, or the quote has
no pending challenge to authorize.
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error409.yaml
'429':
description: >-
Too many requests. Returned with `RATE_LIMITED` when authorization
attempts for this challenge happen too frequently (for example, repeated
bad codes brute-forcing the OTP). The provider may invalidate the
challenge after too many failed attempts. Clients should back off and
retry after the interval indicated by the `Retry-After` response header.
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error429.yaml
'500':
description: Internal service error
content:
application/json:
schema:
$ref: ../../components/schemas/errors/Error500.yaml
Comment thread
jklein24 marked this conversation as resolved.
Loading
Loading