> ## Documentation Index
> Fetch the complete documentation index at: https://agents-docs.lightspark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Per-transaction authorization

> Authorize an SCA-gated money movement

This is the flow you hit most often: an SCA-required customer initiates a money
movement, the quote comes back `PENDING_AUTHORIZATION` carrying an
`scaChallenge`, and you authorize it before the transfer is released. For where
this sits in the wider SCA surface, see the
[overview](https://agents-docs.lightspark.com/platform-overview/sca/overview.md).

> [!NOTE]
>
> **This applies only to customers in a region where Strong Customer Authentication
> is required, in practice customers in the EU (EUR / USDC).** For every other
> customer, none of this appears: money-movement calls complete as usual, no
> `scaChallenge` is returned, and the authorization endpoints are not used. If you
> don't serve EU customers you can skip this section.

Under PSD2, EU e-money and e-money-token (EUR / USDC) money movement must be
confirmed by the end user with Strong Customer Authentication (SCA). Grid wraps
SCA so you satisfy it through the same resources you already use. There is no
separate SCA product to integrate.

### When you'll encounter it

For an SCA-required customer, a money-movement call that would otherwise complete
instead returns the **quote** in status **`PENDING_AUTHORIZATION`** carrying an
**`scaChallenge`** object, and the transfer is **not** released until the
challenge is satisfied. The resulting transaction's webhooks also report
`PENDING_AUTHORIZATION`, but the challenge lives on the quote — fetch it with
`GET /quotes/{quoteId}` using the transaction's `quoteId`. This affects debits
such as:

* Sending EUR / USDC (SEPA and intra-ledger transfers)
* Cross-currency conversions from EUR / USDC (the swap leg)
* On-chain and Lightning withdrawals

EUR / USDC reads are covered by an active SCA login session; non-EUR/USDC
accounts do not require SCA.

### Authentication factors

The `scaChallenge.availableFactors` field tells you which factors the customer
may use. `scaChallenge.factor` is the one in use (default `SMS_OTP`).

| Factor | Enrollment | Per-transaction debit |
| - | - | - |
| `SMS_OTP` | None; a code is sent to the customer's verified phone | ✅ Default |
| `PASSKEY` | Required (WebAuthn credential) | ✅ |
| `TOTP` | Required (authenticator app) | Available only where the challenge is not dynamically linked |

TOTP cannot satisfy a dynamically linked debit because its code cannot be bound
to the amount and payee. For a non-dynamically-linked challenge, use TOTP only
when it appears in `scaChallenge.availableFactors`; that field is authoritative.

Request a specific factor per transaction with the optional top-level `scaFactor`
field on `execute` (`SMS_OTP` default, or `PASSKEY`).

### Satisfying a challenge

Submit an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize` for the
quote that carries the challenge. Provide exactly one of `code` (for `SMS_OTP`)
or `passkeyAssertion` + `origin` (for `PASSKEY`):

```bash theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/authorize \
  -u "$GRID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "000000", "endUserIpAddress": "203.0.113.42" }'
```

The optional `endUserIpAddress` is the IP of the end user's device authorizing
the operation; it feeds the SCA provider's risk assessment, so supply the
customer's address, not your server's.

Authorizing requires an active [SCA login session](https://agents-docs.lightspark.com/platform-overview/sca/login-and-sessions.md):
if it has lapsed, the call returns `409` `SCA_SESSION_REQUIRED` — complete a
fresh login, then authorize again.

> [!WARNING]
>
> **Write your client to loop on status, not on a fixed challenge count.** Treat
> `scaChallenge` as the challenge to satisfy *now*, not necessarily the only one:
> after authorizing, re-inspect the returned quote, and if it is still
> `PENDING_AUTHORIZATION` it carries the **next** `scaChallenge` (a new `id`) —
> authorize that one too and repeat until it leaves `PENDING_AUTHORIZATION`.

Once the quote is in `PENDING_AUTHORIZATION`, authorize it:
`POST /quotes/{quoteId}/authorize`. This is the single authorize path for both
`execute` (pre-funded) and realtime-funding quotes. The challenge — and the SMS
code or passkey assertion that satisfies it — only exists after the challenge is
issued, so the proof is always supplied on this follow-up call, never on the
originating request.

> [!NOTE]
>
> For a **realtime-funding quote**, the `202` / `PENDING_AUTHORIZATION` response
> **withholds `paymentInstructions`** until the challenge is authorized. Authorize
> first, then read `paymentInstructions` from the returned (advanced) quote. If you
> read them off the initial pending response you'll show the customer nothing to
> fund.

If an SMS code lapses before it's used, re-send it. The existing challenge is
reused, and its `expiresAt` is not extended. Use the quote resend endpoint:
`POST /quotes/{quoteId}/authorize/resend`.

```bash theme={null}
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/authorize/resend \
  -u "$GRID_API_TOKEN"
```

If the challenge expires unsatisfied, the transaction fails with
`failureReason: SCA_NOT_COMPLETED` and no funds move. Create a new quote and
have the customer authorize while the new challenge is live.

> [!NOTE]
>
> In **sandbox**, the SMS code is always `000000`.

### Reducing prompts for repeat payees

Trusting a beneficiary (a one-time SCA-gated whitelisting step) lets subsequent
sends to that payee skip the per-transaction challenge. Use this for recurring
payouts to known destinations rather than authorizing every send.

### Calling a customer outside SCA-regulated regions

The authorization endpoints return **`409`** for customers outside SCA-regulated
regions (non-EU), and no `scaChallenge` is ever attached to their transactions.
You don't need to branch on region. Handle `scaChallenge` when it's present and
treat its absence as nothing to do.

## Walkthrough by flow

The mechanics above are the same everywhere: inspect for an `scaChallenge`,
submit an `ScaAuthorization`, and repeat until the resource leaves
`PENDING_AUTHORIZATION`. What differs between flows is *which* call first returns
the challenge and *which* resource you authorize. Here is each one end to end.

> [tab] Send a payment (execute a quote)
>
> The common case — lock a quote, execute it, authorize the quote.
>
> **1. Lock a quote**
>
> `POST /quotes` returns a quote as usual. A standard (prefunded) send carries no
> challenge at quote time.
>
> **2. Execute the quote**
>
> `POST /quotes/{quoteId}/execute` returns the **quote** in `PENDING_AUTHORIZATION`
> with an `scaChallenge`.
>
> **3. Authorize the quote**
>
> `POST /quotes/{quoteId}/authorize` with the proof. Re-inspect the returned quote:
> if it remains `PENDING_AUTHORIZATION`, authorize its next `scaChallenge`. Do not
> assume a fixed number of challenges for a cross-currency or other multi-step
> send.

> [tab] Realtime-funding quote
>
> Here the challenge is issued at *quote* time and `paymentInstructions` are
> withheld until you clear it — you authorize before you fund.
>
> **1. Request the quote**
>
> `POST /quotes` for a realtime-funded send returns `202` /
> `PENDING_AUTHORIZATION` with an `scaChallenge`. `paymentInstructions` are
> **omitted** from this response.
>
> **2. Authorize the quote**
>
> `POST /quotes/{quoteId}/authorize` with the proof — the challenge is carried by
> the quote. Re-inspect the returned quote and continue the authorization loop if
> it remains `PENDING_AUTHORIZATION`.
>
> **3. Read the payment instructions**
>
> Read `paymentInstructions` from the returned (advanced) quote and fund the
> transfer. Reading them off the initial pending response would show the customer
> nothing to fund.

> [tab] Transfer-out (deprecated)
>
> `POST /transfer-out` is **deprecated** — create a quote with an internal account
> source, an external account destination, and `immediatelyExecute: true` for the
> same single-request send, then follow the quote + `execute` flow above (see the
> [quote system](https://agents-docs.lightspark.com/platform-overview/core-concepts/quote-system.md) guide). The legacy
> endpoint has no associated quote, and per-transaction SCA is authorized only on
> the quote resource, so SCA-gated EU debits are not offered on it. For customers
> outside SCA-regulated regions it continues to work until removal.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.