Routing & providers

You call one API. Behind it, Finscale holds connections to multiple acquirers and PSPs, picks the one most likely to approve each transaction, and retries on the next when one fails. This page explains exactly how — and where you can steer it.

Providers are opaque

Every provider in your routing set is an opaque id — prov_eu_acq_01, prov_eu_acq_02, prov_us_psp_01, prov_br_psp_01. You never integrate a provider, sign one, or hold credentials for one. Finscale runs the contracts, the connections, and the compliance behind each id.

The ids are stable, so you can pin rules to them and read them in provider_attempts — but they deliberately carry no company name. That's what makes the abstraction hold:

  • Swap without integration work. When a provider underperforms, Finscale adds, removes, or re-weights ids in your set. Your code never changes.
  • One contract. You have a commercial relationship with Finscale — not with N acquirers in N jurisdictions.
  • One data model. Every provider's response is normalized into the same Payment object, the same error codes, the same settlement lines.

See which providers are active on your account — and their live health — with GET /v1/providers or in the provider health dashboard.

The routing decision

When a payment enters processing, Finscale computes an ordered candidate list. Five stages — first three filter, last two rank:

  1. Method support

    Only providers that process the payment's method stay. For ideal, that's your EU acquiring ids; a US-only PSP is out immediately.

  2. Currency & region

    The provider must process the payment's currency and be licensed for the transaction's corridor. EUR keeps prov_eu_acq_01 and prov_eu_acq_02 in; prov_br_psp_01 is out.

  3. Merchant rules

    Your routing rules apply: block rules reject the transaction outright, prefer_provider moves one id to the front, weight_split distributes by weight. Rules always win over the automatic ranking below.

  4. Health & auth-rate

    Candidates are ranked by live health (error rate, latency, open incidents) and rolling authorization rate for this method × currency segment. A degraded provider drops to the back — or out, if it's failing hard.

  5. Cost

    Among candidates that rank equally, the cheapest processing cost wins. Cost never outranks approval odds — a saved fee is worthless on a declined payment.

The first candidate gets the transaction. The rest become the failover chain. The whole decision runs in single-digit milliseconds inside the payment request.

Automatic failover

The canonical failover story, with the example order ord_9f21_0716 (€49.00): attempt 1 goes to prov_eu_acq_01, which times out. Finscale retries on prov_eu_acq_02, which approves. One API call, two attempts, zero merchant code.

Failover triggers on retriable outcomes only:

OutcomeExamplesFailover
timeout No provider response within the attempt budget (10 s). Yes — next provider
error Provider 5xx, provider rate limit, scheme downtime. Yes — next provider
declined with a retryable decline_code issuer_unavailable, try_again_later — the issuer never gave a verdict. Yes — next provider
declined with a final decline_code card_declined by the issuer, insufficient_funds, fraud block, invalid or expired credentials. Never

Final declines are the issuer's answer — asking a different acquirer the same question gets the same no, burns your auth-rate reputation, and violates scheme retry rules. Finscale fails the payment cleanly instead, and the error tells you why.

  • Up to 3 attempts (primary + 2 failovers) inside one API call.
  • Each failover attempt adds roughly one provider round-trip (p50 ≈ 400 ms).
  • Idempotency is preserved across attempts: one Idempotency-Key, one payment, one charge — no double-processing, whichever provider lands it.
See failover in test mode Create a test-mode card payment with "amount": 4999. Finscale forces a retryable decline on the first provider, fails over, and approves on the second — then shows both attempts in provider_attempts.

The provider_attempts array

Every payment carries its full routing history. Each entry records the provider, the outcome, and the attempt latency — the failover story above looks like this:

GET /v1/payments/pay_8Q2mX4nT1cVb — after failover
{
  "id": "pay_8Q2mX4nT1cVb",
  "object": "payment",
  "amount": 4900,
  "currency": "EUR",
  "status": "succeeded",
  "reference": "ord_9f21_0716",
  "provider": "prov_eu_acq_02",
  "provider_attempts": [
    { "provider": "prov_eu_acq_01", "outcome": "timeout",  "latency_ms": 10004 },
    { "provider": "prov_eu_acq_02", "outcome": "approved", "latency_ms": 391 }
  ],
  "created_at": "2026-07-16T09:24:31Z",
  "livemode": false
}

outcome is one of pending, approved, declined, timeout, error — when it is declined, the normalized reason is in the attempt's decline_code. The top-level provider always equals the provider of the final attempt. Use the array for latency debugging, provider-mix reporting, and support ("why did this take 10 seconds?" — the first attempt timed out).

Routing rules

The automatic ranking is the right default. When you have a reason to override it — a better negotiated rate, a provider you're evaluating, a method you want isolated — attach routing rules to your account with POST /v1/routing_rules.

A rule has a match (which payments it applies to: method, currency, country, amount_gte) and an action. Rules are evaluated in ascending priority; the first match applies. Three actions cover the practical cases:

Prefer a provider for EUR cards — failover stays on
{
  "id": "rr_2Nk8sQd1Vw4p",
  "object": "routing_rule",
  "priority": 10,
  "match": { "method": "card", "currency": "EUR" },
  "action": { "type": "prefer_provider", "provider": "prov_eu_acq_01" },
  "active": true
}

prefer_provider moves the provider to the front of the candidate list. Everything else is unchanged — if it returns a retryable decline, errors, or times out, failover proceeds down the ranked chain.

Rules are a scalpel, not a default Every prefer_provider rule overrides live health and auth-rate ranking. A preferred provider having a bad day drags your conversion with it. Prefer weight_split for experiments, review rules quarterly, and delete the ones you can't explain.

What you see vs. what Finscale handles

The contract of the abstraction, in one table:

You seeFinscale handles
One REST API and one Payment objectN provider APIs, formats, and protocol quirks
Opaque ids in provider and provider_attemptsProvider contracts, onboarding, credentials, compliance
One unified error modelHundreds of provider-specific decline codes, mapped
Refund by payment id — POST /v1/refundsWhich provider processed the original, and its refund rail
One settlement report and one payout per currencyPer-provider settlement files, timing differences, netting
Routing rules and a health dashboardLive health scoring, auth-rate tracking, failover execution

If you ever need the provider dimension — for finance, for support, for curiosity — it's on every payment and every settlement line. You just never have to build against it.