Skip to main content

Provider landscape — the vendor capability matrix

What this covers / who it's for. A cross-vendor evidence base — every finance-adjacent vendor with code in this repo, what it can serve against our 17 capability contracts, what is actually routed to it today, and what remains unverified. This is the evidence base for a future provider-selection decision, not the decision itself. The first distinction that matters throughout: ROUTED (a live jurisdiction_provider_grants row can send it traffic) vs DORMANT (a transport SDK with zero app consumers). An honest ❔ UNVERIFIED cell is deliberate — this page would rather under-claim than guess.

1. Legend + evidence rules

SymbolMeaning
Serves the capability as our contract models it
Partial — serves it with a named gap (stubbed method, missing sub-shape, semantic mismatch)
Does not serve it (confirmed absent from the adapter/spec/docs)
UNVERIFIED — not established this pass; a vendor question, not a claim

Evidence required per vendor class before a cell is anything but ❔:

  • Routed (Conduit, Sumsub, Utila, Fingerprint, SEON) — the adapter class's implements list (§2, §3) plus any ProviderOperationNotSupportedException stub inside it. Re-verify by reading the adapter class declaration.
  • Bridge (dormant SDK) — the committed packages/stables/bridge-sdk/spec/openapi.json snapshot for request/response shapes, and the live apidocs.bridge.xyz pages for semantics (behavior a spec shape alone doesn't state). Every Bridge fact below carries its page URL and "fetched 2026-09-02". Re-verify by re-fetching the cited page, or running make bridge-spec-diff for a shape check — method per the bridge-development skill (llms.txt → fetch the .md page → cite URL + date).
  • Sovera (dormant SDK) — packages/stables/sovera-sdk/README.md's resource list, "Spec ambiguities appendix" (README ~L420) and "Onboard an end user (Crypto)" section, since there is no committed OpenAPI spec for it (hand-authored against a vendor doc not in this repo). Re-verify against that README; gaps there are pending the vendor, not resolvable in-repo.
  • sFOX (dormant SDK) — not assessed this pass. Every cell is ❔ by policy, not by omission.

2. Vendor roster

VendorIn-repo packageStatusAdapter classFinanceProvider case?config/rails.php entry?ADR
Conduitpackages/stables/conduit-sdkROUTED primary (FiatRails, Conversion, + Onboarding/Custody/Fraud outside their flips)Conduit\ConduitRailsProviderYesYesdecisions D94
Sumsubpackages/stables/sumsub-sdkROUTED primary (Onboarding, GB+EU only)Sumsub\SumsubRailsProviderYesYesD95
Utilapackages/stables/utila-sdkROUTED primary (Custody, all six baseline jurisdictions)Utila\UtilaRailsProviderYesYesD98
Fingerprintpackages/stables/fingerprint-sdkROUTED primary (Fraud, all six)Fingerprint\FingerprintRailsProviderYesYesD102
SEONpackages/stables/seon-sdkROUTED permitted (Fraud fallback, all six)Seon\SeonRailsProviderYesYesD102
Bridgepackages/stables/bridge-sdkDORMANT SDK — zero app-modules/* importsnoneNoNoD114 (SDK drift-gate posture only, no adapter)
Soverapackages/stables/sovera-sdkDORMANT SDK — zero app-modules/* importsnoneNoNonone
sFOXpackages/stables/sfox-sdkDORMANT SDK — zero app-modules/* importsnoneNoNoD111 (ingestion posture only, no adapter)

Source: app-modules/rails/src/{Conduit,Sumsub,Utila,Fingerprint,Seon}/*RailsProvider.php class declarations; app-modules/rails/config/rails.php (all five routed vendors enabled => true); app-modules/foundation/src/ValueObjects/FinanceProvider.php (5 cases: Conduit, Sumsub, Utila, Fingerprint, Seon); app-modules/rails/src/Providers/RailsServiceProvider.php (ProviderAdapterRegistry seeded with the same 5).

3. The capability matrix

17 rows: 13 umbrella capabilities (Modules\Rails\Contracts\RailsProvider extends list) + 4 segregated (off-umbrella) capabilities. Grouped by the routing CapabilityGroup (app-modules/rails/src/Support/CapabilityGroupMap.php). Column order matches the roster above.

GroupCapability (method(s))ConduitSumsubUtilaFingerprintSEONBridgeSoverasFOX
OnboardingOnboardsCustomers (getOnboardingRequirements, submitApplication, getApplication)◐ [1]◐ [2]
OnboardingManagesCustomers (getCustomer)✅ [3]◐ [4]
OnboardingManagesDocuments (uploadDocument)✗ [5]◐ [6]
OnboardingVerifiesIdentity (initiateVerification)✅ [7]
OnboardingFetchesVerification (fetchVerification) — segregated✅ [7]
OnboardingVerifiesBusinesses (fetchBusinessVerification, fetchBeneficialOwners) — segregated◐ [8]
FraudAssessesFraudRisk (assess) — segregated✗ [9]
CustodyManagesWallets (getWallet, listWallets, createWallet, rotateWallet)◐ [10]◐ [11]◐ [12]
CustodyManagesWalletSigners (5 methods)◐ [13]❔ [14]
CustodyManagesSigningQuorum (4 methods)◐ [13]❔ [14]
CustodyManagesCustodyTransactions (getTransaction, listTransactions, initiateTransaction, voteOnTransaction) — segregated❔ [15]
CustodyManagesRegisteredAddresses (registerAddress, getRegisteredAddress, listRegisteredAddresses, revokeRegisteredAddress)◐ [16]◐ [17]
FiatRailsManagesVirtualAccounts (getVirtualAccount, listVirtualAccounts)◐ [18]❔ [19]
FiatRailsManagesPayouts (createPayout, getPayout, cancelPayout, payoutRequirements)✅ [20]❔ [19]
FiatRailsManagesWhitelistRecipients (5 methods)◐ [21]
FiatRailsManagesTransactions (getTransaction, listTransactions, submitSenderInformation)◐ [22]◐ [23]
ConversionManagesOrders (createOrder, getOrder, listOrders, cancelOrder, orderRequirements)◐ [24]◐ [25]

Footnotes (each cites the page/file checked this pass; Bridge dates are all 2026-09-02):

  1. Bridge POST /customers + KYC Links create the onboarding intent; no separate "requirements"/"application" resource — one customer-creation flow serves both individual and business. kyc_status states include awaiting_questionnaire/awaiting_ubo. No analogue of our per-country getOnboardingRequirements read was found on the fetched pages — the requirements-read half is UNVERIFIED, hence partial. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md , https://apidocs.bridge.xyz/api-reference/kyc-links/get-all-kyc-links.md
  2. Sovera: KYC is a "flow" via users()->create/activate, not a distinct application resource; business end users are undocumented (see [4]). packages/stables/sovera-sdk/README.md "Onboard an end user (Crypto)" (~L250–299).
  3. Bridge GET /customers/{id} — same customer resource as onboarding. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md
  4. Sovera users()->get (individual only, per [2]). packages/stables/sovera-sdk/README.md "Resources & methods" (~L342–361). Individual end users only — reading or creating a business end user is undocumented (UNVERIFIED), so the cell is partial, not full.
  5. Sumsub\SumsubRailsProvider deliberately omits ManagesDocuments — Sumsub's own WebSDK collects documents, so there is nothing for our adapter to forward. app-modules/rails/src/Sumsub/SumsubRailsProvider.php class declaration.
  6. Bridge has no upload endpoint. Documents ride as base64 fields on the customer payload: identifying_information[].image_front/image_back and documents[].file (with a purposes enum). A structurally different shape from a document-upload capability — not a 1:1 fit. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md
  7. Bridge's kyc_status is queryable on the same customer resource both at initiation and as a pull — no separate "initiate" vs "fetch" split like ours, so both cells map to the one read/poll surface. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md
  8. Bridge KYB rides type: business customers + associated_persons (UBO threshold 25%+, has_control: true for a control person) — a UBO graph is readable via associated persons, but whether it is walkable the same way our fetchBeneficialOwners needs (cycle-safe, depth-bounded) is unconfirmed — mark the graph-walk semantics UNVERIFIED. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/customers/compliance/businesses/overview.md , .../ownership.md
  9. No fraud-scoring/device-risk endpoint in the spec. The only fraud/risk hits in packages/stables/bridge-sdk/spec/openapi.json are a boolean fraud flag + FraudParameter query param on ACH returns, and risk_rejection_reason on funds requests — return-side flags, not an assessment/scoring surface. Grepped spec/openapi.json (lines ~13, 3195, 7705, 8833) this pass.
  10. Utila stubs rotateWallet with ProviderOperationNotSupportedException (terminal 422); read + create are served. app-modules/rails/src/Utila/UtilaRailsProvider.php ~L193–206.
  11. Bridge custodial wallets exist (platform/wallets/overview.md) with create/list/get, but no rotate/replace-key operation is documented. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/wallets/overview.md
  12. Sovera wallets()->create, list, balance, transactions — no rotate. packages/stables/sovera-sdk/README.md "Resources & methods" (~L342–361).
  13. Utila advertises both interfaces (Console-managed policy engine) but every one of their 5+4=9 methods throws ProviderOperationNotSupportedException — advertised-but-stubbed, not a genuine partial. app-modules/rails/src/Utila/UtilaRailsProvider.php ~L453–496.
  14. No wallet co-signer/quorum-management endpoint found in the Bridge spec or wallets docs — mark ❔ rather than ✗ because the wallets docs describe initiation_required/SCA metadata whose full semantics were not chased down this pass. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/wallets/move-money.md
  15. Bridge custodial wallets support movement via Orchestration APIs, but no approver/vote model or transaction-list-with-approval-state resource is documented — UNVERIFIED whether a transaction read/list surface maps to this capability at all. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/wallets/overview.md
  16. Utila stubs revokeRegisteredAddress; register/read/list are served. app-modules/rails/src/Utila/UtilaRailsProvider.php ~L323–332.
  17. Bridge liquidation addresses are a distinct concept — a permanent route FROM a blockchain address TO a fiat or blockchain destination, not a withdrawal-address allow-list on a wallet. Mapping liquidation addresses onto ManagesRegisteredAddresses would be a semantic stretch; treat as ◐ pending a real mapping exercise, not ✅. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/orchestration/liquidation_address/liquidation_address.md
  18. Bridge virtual accounts exist but are transport-only: no page states a VA holds a balance, and destinations are currency/payment_rail/address/bridge_wallet_id — no fiat destination in the enum, i.e. auto-convert-to-crypto only. Our ManagesVirtualAccounts models a balance-bearing account (Conduit shape) — a semantic mismatch, not a clean fit. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/virtual-accounts/create-a-virtual-account.md , https://apidocs.bridge.xyz/platform/orchestration/virtual_accounts/virtual-account.md
  19. Sovera has no virtual-accounts or fiat-rails resource in its Crypto or Cards resource lists — absent, not gapped-with-a-note. packages/stables/sovera-sdk/README.md "Resources & methods" (~L342–361) and "Cards resources & methods" (~L507–555).
  20. Bridge transfers (with a developer_fee/conversion path) serve payout-shaped money movement to an external account; states/lifecycle documented. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/orchestration/transfers/transfer-states.md
  21. Bridge external accounts (USD routing+account, EUR IBAN+BIC, MXN CLABE, BRL Pix key, GBP sort code, COP BreB key) are the closest analogue to a whitelist recipient, but there is no dedicated "recipient" resource separate from "external account" — a naming/shape difference, not a 1:1 capability match. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/orchestration/external-accounts/external-accounts-api.md
  22. Bridge transfer reads exist; travel_rule_data on transfers covers the sender-information piece, but the shape differs from our deposit-side submitSenderInformation (Conduit-specific RFI flow) — same intent, different mechanics. Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/guides/move-money/crypto-travel-rule.md
  23. Sovera has transfers() (crypto wallet-to-wallet) and withdrawals(), but no travel-rule sender-information submission surface documented. packages/stables/sovera-sdk/README.md "Resources & methods" (~L342–361).
  24. Bridge has no first-class "order" resource: fiat-to-fiat conversion rides POST /v0/transfers (the "Stablecoin Sandwich" — Bridge executes the FX at market rate and pays out on the destination rail), and a "fixed output" transfer lets the destination amount be specified upfront with no separate quote/lock step; cancellation is only documented for transfers still in awaiting_funds, and no requirements read exists — so cancel + requirements semantics of our ManagesOrders are not matched. Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/guides/move-money/fiat_to_fiat.md , https://apidocs.bridge.xyz/get-started/guides/move-money/fixed_outputs_integration_guide.md , https://apidocs.bridge.xyz/platform/orchestration/transfers/transfer-states.md
  25. Sovera trading()->requestQuote, placeOrder — quote+order only, no get/list/cancel documented (ambiguity #8). packages/stables/sovera-sdk/README.md "Spec ambiguities appendix" (~L444).

Tally: Conduit 13✅ / 0◐ / 4✗ / 0❔ (full umbrella, none of the 4 segregated). Sumsub 5✅ / 0◐ / 12✗ / 0❔ (5 onboarding capabilities exactly). Utila 1✅ / 4◐ / 12✗ / 0❔ (custody-tx clean; wallets + registered-addresses genuinely partial; signers/quorum advertised-but-fully-stubbed). Fingerprint 1✅ / 0◐ / 16✗ / 0❔. SEON 1✅ / 0◐ / 16✗ / 0❔. Bridge 4✅ / 9◐ / 1✗ / 3❔ (✅ ManagesCustomers, VerifiesIdentity, FetchesVerification, ManagesPayouts; ✗ AssessesFraudRisk; ❔ ManagesWalletSigners, ManagesSigningQuorum, ManagesCustodyTransactions). Sovera 0✅ / 5◐ / 0✗ / 12❔. sFOX 0✅ / 0◐ / 0✗ / 17❔ (not assessed, by policy).

4. Live routing state

Grants realized by features:sync from app-modules/features/src/Providers/FeaturesServiceProvider.php (~L145–193); all shipped data_region null (no residency enforcement — a documented pre-product deferral, not an oversight):

JurisdictionsCapabilityGroupPermitted keysPrimaryADR
ZA, NG, KE, GB, EU, USevery group (baseline)conduitconduitD94
GB, EUOnboardingsumsub, conduitsumsubD95
ZA, NG, KE, GB, EU, USCustodyutila, conduitutilaD98
ZA, NG, KE, GB, EU, USFraudfingerprint, seonfingerprintD102

Net effective primaries: Onboarding = sumsub (GB/EU) / conduit (ZA/NG/KE/US); Custody = utila everywhere (conduit override-only — it lacks ManagesCustodyTransactions, so it is never an automatic custody-tx fallback); Fraud = fingerprint everywhere (seon permitted; conduit demoted, not permitted — it implements no fraud capability at all); FiatRails + Conversion + the 6 placeholder CapabilityGroup cases with no capability contract yet = conduit everywhere.

Fail-closed refusal reasons (ProviderNotRoutableException, 403, machine-readable reason, app-modules/features/src/Support/ProviderRouter.php): seven reasons total — blank_jurisdiction, inactive_jurisdiction, no_grant, override_not_permitted, no_primary, residency_conflict (the grant's data_region doesn't match the jurisdiction's region) and missing_dpa (a distinct reason: the grant's data_region is non-null and the residency check passed, but its dpa_ref is empty) — both moot today since every grant ships data_region null — plus BusProviderGuard's no_tenant_context. Never a silent fallback.

Routing is orthogonal to the money-path bus gates: every money-movement command also carries the account-freeze gate and the D113 RequiresApprovedOnboarding gate (decision log) regardless of which provider a capability routes to.

Override mechanism. tenant_provider_overrides (tenant-RLS, model Modules\Features\Models\TenantProviderOverride): capability_group, provider_key, valid_until (nullable). The router accepts an override only among the jurisdiction's permitted keys for that group. No bus command, artisan command, or backoffice surface writes this table today — rows are written directly (ops tooling only).

RequiresProvider marker coverage (bus-gated commands, grepped this pass): 12 command files — 9 accounts wallet/signer/quorum makers (CreateWallet, RotateWallet, AddSigner, RemoveSigner, PromoteSigner, DemoteSigner, SetQuorumThreshold, OverrideWalletQuorum, ClearWalletQuorumOverride), 2 onboarding makers (SubmitOnboardingApplicationCommand, InitiateVerificationCommand), 1 payments maker (RegisterAddressCommand). The custody-controls InitiateCustodyTransactionCommand deliberately carries no RequiresProvider (it dispatches into the approval-quorum platform, not directly to rails); the same fail-closed check is re-implemented off-bus, at real-forward time, in CustodyTransactionSubmitter via ProviderGuard::ensureResolvable — a SYSTEM-context analogue of the marker, not a gap. FiatRails, Conversion, and funding makers carry no gate yet — the bind-time resolver there is fail-SAFE→default, not fail-closed. Built-vs-deferred detail: docs/tracking/multi-provider/00-provider-router.md.

5. Webhook / event parity

VendorValidator schemeEvents registered todayConsuming mirror domain(s)
ConduitHMAC-SHA256, single header, ~300s replay tolerancecustomer.*, application.*, wallet.*, virtual_account.*, signer.*, quorum.updated, account.freeze, transaction.*onboarding, accounts, funding, payments
SumsubTwo-header HMAC (X-Payload-Digest + -Alg, HMAC-256/512/1)applicantCreated, applicantReviewed, applicantPending, applicantOnHold, applicantWorkflowCompleted, applicantWorkflowFailed, companyStructureChangedonboarding (pull-authoritative — signal only, then re-pull)
UtilaAsymmetric RSASSA-PSS/SHA-512TRANSACTION_CREATED, TRANSACTION_STATE_UPDATED, TRANSACTION_AML_SCREENING_RESULT_READY, TESTaccounts custody_transactions mirror (pull-to-hydrate)
FingerprintSingle-header HMAC-SHA256identification (synthesised type)fraud (not a mirror domain — device-risk verdicts, no mirror table)
SEONnone (webhook-less, sync post-and-score)
BridgeNone built. Docs describe X-Webhook-Signature: t=<ts>,v0=<base64>, RSA+SHA-256 PKCS1v15 over timestamp.payload, reject >10min old, per-endpoint PEM public key. event_categories: customer, kyc_link, liquidation_address.drain, static_memo.activity, transfer, virtual_account.activity, bridge_wallet.activity, card_account, card_transaction, card_withdrawal, posted_card_account_transaction, external_account, rfi. Retry policy not stated on any fetched page — ❔ vendor question. Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/introduction/quick-start/setting-up-webhooks.md , https://apidocs.bridge.xyz/api-reference/webhooks/create-a-webhook-endpoint.mdnone
SoveraNone built. SDK models webhook CRUD + verify() only; event schemas undocumented by the vendor (packages/stables/sovera-sdk/README.md "Spec ambiguities appendix" #1, ~L425).none

Today the Conduit + Sumsub + Utila events above are consumed by onboarding, accounts, funding, and payments (payments builds on the same mirror framework for its payout/order mirrors); treasury, the fourth mirror-domain module, is table-less (rates only) and consumes no webhook. See Provider integration for the full pipeline mechanics — not restated here.

6. Asset & chain coverage

VendorChainsFiat railsStablecoins (where evidenced)
Foundation Chain (our vocabulary)10: Ethereum, Polygon, Arbitrum, Base, Optimism, Avalanche, Solana, Tron, Stellar, Bitcoin (bsc deliberately excluded, D96 notes)n/a (fiat rides Conduit/other adapters, not this enum)n/a
Utila (UtilaChainMap)All 10, incl. Bitcoin → networks/bitcoin-mainnetn/an/a (custody rail; asset ticker mapping via UtilaAssetMap, config-driven, fail-loud)
Conduit❔ UNVERIFIED — the mapper does Chain::tryFrom(...) ?? throw ProviderValidationException against the neutral enum; the exact set of chains the Conduit SDK itself advertises was not extracted this pass (would need the Conduit SDK's own chain enum, out of the rails module this page cites).n/an/a
Fingerprint / SEONn/a (fraud-scoring only, not chain/asset-bearing)n/an/a
Bridge10+ chains, verbatim: EVM — Arbitrum, Avalanche C-Chain, Base, Celo, XDC, HyperEVM, Ethereum, Linea, Monad, Optimism, Polygon, World Chain; non-EVM — Solana, Stellar, Sui, Aptos, Tron, Plasma, Tempo. Bitcoin is NOT listed on any fetched page (chains page, payment-routes page, transfer payment_rail enum, liquidation-address chain enum, Bridge-wallet chain enum). Custodial wallets specifically: Base, Ethereum, Solana, Tempo, Tron (+xdc in the API enum). Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/guides/move-money/supported-blockchains.md , https://apidocs.bridge.xyz/get-started/introduction/what-we-support/payment-routes.md , https://apidocs.bridge.xyz/platform/wallets/overview.mdUSD (ACH/FedNow/Wire), EUR (SEPA), MXN (SPEI), BRL (Pix), GBP (Faster Payments), COP (Bre-B + bank transfer). Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/introduction/what-we-support/payment-routes.mdUSDC, USDT, USDB, PYUSD, EURC, USDP, USDG, CASH, USDSUI, USDCBL, PATHUSD ("USDC & EURC are the only stablecoins supported for users in the EEA"). Same source.
Sovera❔ UNVERIFIED-IN-REPO — no chain enum found in the SDK source; wallets are vault-network-keyed (vault().networks()), not chain-enum-keyed. packages/stables/sovera-sdk/README.md "Resources & methods" (~L354).❔ (no fiat-rails resource — see matrix §3 footnote 19)
sFOX❔ not assessed

7. Semantic deltas a tick hides

A ✅/◐ cell above says a vendor has a resource in the same neighborhood — not that it behaves the same way. These are the deltas worth knowing before routing anything real to a new vendor.

(a) Virtual accounts. In-app (Conduit): balance-bearing deposit accounts, mirrored in accounts (the virtual_accounts mirror + the wholesale-overwritten account_balances projection). Bridge: transport-only — "permanent, reusable fiat deposit addresses that convert incoming fiat into crypto and deliver it to your specified destination"; destinations carry currency/payment_rail/address/bridge_wallet_id, no fiat destination in the enum; no page states a VA holds a balance. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/orchestration/virtual_accounts/virtual-account.md , https://apidocs.bridge.xyz/api-reference/virtual-accounts/create-a-virtual-account.md . Sovera: ❔ (no VA resource — §3 footnote 19).

(b) Custody / co-sign. In-app: an N-of-M in-app approval-quorum engine (custody-controls, D97) — amount-tiered N-eyes consensus among the tenant's own people, BEFORE the request ever forwards to a provider; provider-agnostic by design. Conduit's older model was a provider-hosted co-sign ceremony (a verificationUrl a human visits) — dropped in D96 because it had no neutral representation once MPC vendors (Utila, and the Fireblocks/Fordefi set it was evaluated against) proved signing is an API vote, not a hosted URL. Utila: signing is voteOnTransaction, an API call, consistent with the D96 rewrite. Bridge: no approver/co-signer/key-management model documented for custodial wallets; initiation_required and an SCA initiation object exist but read as metadata, not an approval workflow. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/wallets/move-money.md . Sovera: ❔.

(c) Documents vault. In-app (D43): Stables stores its own encrypted copy first, then ForwardDocumentToProviderJob streams it to the rail via ManagesDocuments::uploadDocument (Conduit only — Sumsub collects via its own WebSDK, so the forward is skipped for that route, not 500'd, per ProviderCapabilityInspector). Bridge: no upload endpoint at all — documents ride as base64 fields (identifying_information[].image_front/image_back, documents[].file) on the customer create/update payload, with a purposes enum. A different shape entirely — a document-forward job built for Conduit's model would need a real rewrite, not a new translator. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md . Sovera: ❔.

(d) Travel rule. In-app: ManagesTransactions::submitSenderInformation (Conduit, deposit-side, the funding module's RFI flow). Utila: registerAddress drops originatorDetails + selfCustodyAttestation unconditionally, across all six granted jurisdictions (D98's accepted regulatory risk; UtilaRailsProvider ~L207–269; remediation tracked in docs/tracking/multi-provider/20-custody.md, not restated here). Bridge: travel_rule_data attachable to transfers/VAs/liquidation addresses, or a one-off POST /v0/travel_rule_data/:id; quoted thresholds — EEA "no threshold" on the rail itself but "exceeding €1,000" triggers self/hosted-wallet attestation, US "above USD 3,000"; the FAQ states Bridge will not hold or freeze funds for missing Travel Rule data. Fetched 2026-09-02: https://apidocs.bridge.xyz/get-started/guides/move-money/crypto-travel-rule.md and its FAQ page.

(e) Developer fees. In-app: no such concept — fees are entitlement-driven (e.g. the payments payments.payout-volume-fee meter), not a per-vendor mechanism. Bridge: a documented per- transaction mechanism — dedicated ledger, monthly USD settlement on the 5th, developer_fee / developer_fee_percent fields on transfers, developer_fee_percent/beta fee_config on VAs, an account-default + per-address override on liquidation addresses. Fetched 2026-09-02: https://apidocs.bridge.xyz/platform/orchestration/fees-and-mins/devfees.md . Sovera/sFOX: ❔ (sFOX's README headline lists configurable per-user fees — packages/stables/sfox-sdk/README.md L16; capability mapping not assessed this pass).

(f) Onboarding subject / eligibility. In-app (D110): the client declares the onboarding SUBJECT explicitly — individual|business, no silent default. Bridge: type is individual|business on customer create; the high_risk_activities enum verbatim lists gambling as an enumerated high-risk activity, but no fetched page states whether a gambling-flagged business is eligible to onboard at all — UNVERIFIED, a vendor question, not an assumption either way. Fetched 2026-09-02: https://apidocs.bridge.xyz/api-reference/customers/create-a-customer.md . Sovera: business end-user creation is undocumented entirely (§3 footnote 4).

8. Dormant SDK inventory + maintenance

bridge-sdk (packages/stables/bridge-sdk/README.md) — pure Saloon v4 transport, no domain logic, no app consumer. 24 tag resources (README "Resources", L129–159: Cards, Customers, AssociatedPersons, KycLinks, Transfers, ExternalAccounts, Plaid, VirtualAccounts, StaticMemos, PrefundedAccounts, LiquidationAddresses, Wallets, Webhooks, Developers, ApiKeys, FiatPayoutConfiguration, Sandbox, CryptoReturnPolicies, Rewards, Lists, BatchSettlements, FundsRequests, TravelRule, ExchangeRates). README states "128 operations across 24 tag resources" (README L15); an independent grep of the committed spec/openapi.json this pass (grep -oE '"(get|post|put|patch|delete|head|options)":' spec/openapi.json | wc -l) agrees: 128. spec/openapi.json snapshot last touched 2026-09-02 (README "Known live drift" note, L296–299 — a real drift-loop exercise already absorbed two schema changes). spec/exclusions.json is empty ([]). D114 drift loop: make bridge-spec-diff [ARGS="--update"]; tests/SpecCoverageTest.php reddens only on added/removed operations, never on changed-shape or schema-only drift (README "Tracking upstream API changes", L260–299). Inbound webhook ingestion is design-only — README "Inbound webhooks — the future ingestion slice (design, not built)" (L317). KYC-error redaction note: exception messages from customer/KYC endpoints can echo submitted field values back — route through this app's redaction/DLP path before logging (README "Error handling").

sovera-sdk (packages/stables/sovera-sdk/README.md) — two connectors, one facade: Crypto (Stables\Sovera\Crypto\*, ~50 endpoints, 3 auth tiers — SX Customers session, SX Connect x-api-key, SX Users bearer) and Cards (Stables\Sovera\Cards\*, ~45 endpoints, single x-api-key). No committed OpenAPI spec — hand-authored against a vendor document cited as "spec §1…§9", not itself in the repo. "Spec ambiguities appendix" (README L420) and "Cards spec ambiguities appendix" (L600) carry the gaps: webhook event schemas undocumented, account_type enum conflict kept as plain string, a users/ path oddity in trading, a wallet-balance response shape conflict, /currency-pairs referenced but unspecified, fiat withdrawals with error codes but no endpoint, production base URL undocumented (defaults sandbox). Open vendor item: business end-user creation is "not a documented flow" (README L294–299) — an open integration-team question with the vendor.

sfox-sdk (packages/stables/sfox-sdk/README.md) — Saloon connector for sFOX Connect (partner master account + end-user sub-accounts, trading, custody, funding, withdrawals, transfers, per-user fees; REST + a WebSocket message layer). Pure plumbing, no adapter, no consumer. ADR D111 covers only its ingestion posture (REST polling first) — not a capability commitment. Not assessed against the capability matrix this pass (§3, §6 both ❔ throughout).

Turning any of these three dormant SDKs into a routed provider means writing an adapter behind the same neutral capability seam this page's §2–§4 describe — see the multi-provider-development skill for the seam's conventions (adapter/RailsProvider capability interfaces, exception translation, ProviderAdapterRegistry, the router).

9. Questions for the vendor

Bridge:

  • Is there a BTC roadmap? Every fetched chain-related page omits it entirely.
  • What is the webhook retry schedule, and what auto-disables an endpoint? (resolves §5's Bridge webhook row retry-policy ❔)
  • What is the wallet signing/key model, and is there any customer-side approver/quorum concept? (resolves §3 rows ManagesWalletSigners/ManagesSigningQuorum/ManagesCustodyTransactions and §7(b))
  • Does the DocSend "Bridge Business Onboarding (KYB) Guide" fully replace manual document submission, or is documents[] a subset? (resolves §3 ManagesDocuments footnote 6)
  • Is there a per-country onboarding-requirements read (an analogue of our getOnboardingRequirements)? (resolves §3 OnboardsCustomers footnote 1)
  • Can a virtual-account destination ever be fiat, or is auto-convert-to-crypto the only mode? (resolves §7(a))
  • Which endorsements gate which rails for a business, and is a gambling-flagged business eligible to onboard at all? (resolves §7(f))
  • What is the Idempotency-Key retention window, and what is the GA status of fee_config / non-USD fee settlement?

Sovera:

  • How does a business (non-individual) end user get created on the Crypto side? (resolves §3 footnote 2, §7(f))
  • What are the webhook event schemas (types, payload shapes, signature scheme, retry policy)? (resolves §5's Sovera webhook row)
  • Is there a virtual-accounts or fiat-rails resource at all, on either Crypto or Cards? (resolves §3 footnote 19, §6, §7(a))
  • What is the production base URL, and how are rate-limit headers named?
  • Are trading list/cancel and fiat withdrawal endpoints planned?

sFOX: not assessed this pass — the first open question is simply which of the 17 capabilities it can serve at all; no matrix cell here should be treated as anything but ❔ until that pass runs.

Routed-vendor unknowns: Conduit's exact supported-chain list was not extracted from the SDK this pass (§6) — worth resolving before citing Conduit chain coverage as complete anywhere else.

10. Re-verification checklist

Before trusting a cell on this page:

  • Re-read the adapter class declaration (implements list) for any routed-vendor cell — app-modules/rails/src/{Conduit,Sumsub,Utila,Fingerprint,Seon}/*RailsProvider.php.
  • Re-check the assignIn/registerConduitBaseline lines in app-modules/features/src/Providers/FeaturesServiceProvider.php for §4's grant table — a new grant changes the "net effective primaries" summary.
  • Re-run make bridge-spec-diff for any Bridge shape claim, and re-fetch the specific apidocs.bridge.xyz page cited for any Bridge semantic claim — both can drift independently.
  • Re-read packages/stables/sovera-sdk/README.md's ambiguities appendices for Sovera cells — they are pending the vendor's own spec, not resolvable from this repo alone.
  • Re-grep RequiresProvider implementers if a new provider-sensitive command ships, before restating §4's coverage count.

← Engineering wiki index