> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-feat-striga-sca-docs.mintlify.site/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, it comes back `PENDING_AUTHORIZATION` with an `scaChallenge`, and you
authorize it before the transfer is released. For where this sits in the wider
SCA surface, see the [overview](/platform-overview/sca/overview).

<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.
</Note>

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 transaction (or quote) in status **`PENDING_AUTHORIZATION`**
with an **`scaChallenge`** object, and the transfer is **not** released until the
challenge is satisfied. 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": "123456" }'
```

<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`.
</Warning>

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.
</Note>

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"
```

<Note>
  In **sandbox**, the SMS code is always `123456`.
</Note>

### 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.

<Tabs>
  <Tab title="Send a payment (execute a quote)">
    The common case — lock a quote, execute it, authorize the quote.

    <Steps>
      <Step title="Lock a quote">
        `POST /quotes` returns a quote as usual. A standard (prefunded) send carries no
        challenge at quote time.
      </Step>

      <Step title="Execute the quote">
        `POST /quotes/{quoteId}/execute` returns the **quote** in `PENDING_AUTHORIZATION`
        with an `scaChallenge`.
      </Step>

      <Step title="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.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Realtime-funding quote">
    Here the challenge is issued at *quote* time and `paymentInstructions` are
    withheld until you clear it — you authorize before you fund.

    <Steps>
      <Step title="Request the quote">
        `POST /quotes` for a realtime-funded send returns `202` /
        `PENDING_AUTHORIZATION` with an `scaChallenge`. `paymentInstructions` are
        **omitted** from this response.
      </Step>

      <Step title="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`.
      </Step>

      <Step title="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.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Transfer-out">
    `transfer-out` has no associated quote, and per-transaction SCA is authorized
    only on the quote resource, so SCA-gated EU debits are **not** offered on this
    endpoint — use the quote + `execute` flow above for EU customers. For customers
    outside SCA-regulated regions, `transfer-out` proceeds as usual.
  </Tab>
</Tabs>
