Onramp endpoints
stridge.onramp is the buy-crypto-with-fiat surface — the SDK side of the Kit's Cash rail. Ten methods, all going through projectHttpClient: each call sends the configured projectKey as X-Gateway-Key, the same auth as gateway.* and balance.*.
The shape mirrors the user journey: browse the catalog (what fiats / crypto / payment methods are available for the country), price a quote, create a session (which binds the quote, allocates the deposit address, and returns the provider's hosted-checkout URL), then poll that session until it reaches a terminal state.
The onramp request and response bodies are snake_case on the wire (fiat_currency, checkout_url, flow.entry.settlement_state), and the SDK passes them through verbatim — so the field names below are exactly what you read and write. The per-call options argument (clientCountry, idempotencyKey, ifNoneMatch, signal) is the one camelCase surface; those are SDK-level options, not body fields.
| Method | HTTP | Returns |
|---|---|---|
onramp.catalog | GET /gateway/onramp/catalog | OnrampCatalogResponse |
onramp.fiats | GET /gateway/onramp/fiats | OnrampFiatsResponse |
onramp.crypto | GET /gateway/onramp/crypto | OnrampCryptoResponse |
onramp.countries | GET /gateway/onramp/countries | OnrampCountriesResponse |
onramp.paymentMethods | GET /gateway/onramp/payment-methods | OnrampPaymentMethodsResponse |
onramp.quote | POST /gateway/onramp/quote | OnrampQuoteResponse |
onramp.createSession | POST /gateway/onramp/sessions | OnrampSessionResponse |
onramp.getSession | GET /gateway/onramp/sessions/{id} | OnrampSessionResponse |
onramp.listOwnerSessions | GET /gateway/onramp/owners/{owner}/sessions | OnrampSessionListResponse |
onramp.setSessionState | PUT /debug/gateway/onramp/sessions/{id}/state | OnrampSessionResponse |
Every method accepts an optional final options argument that takes an AbortSignal (and, where it makes sense, server-side filters). Country resolution is shared across the reads: pass an explicit country (ISO-3166-1 alpha-2), or a softer clientCountry hint that maps to the X-Client-Country header; absent both, the gateway resolves via GeoIP, then the tenant default. All return types are unwrapped — the SDK strips the { data, success, message } envelope for you.
onramp.catalog
One-call catalog for first paint — the fiats the user can pay in, the crypto they can buy, and the payment methods available, all for the resolved country. Use this to populate a currency picker without fanning out to the scoped reads.
Signature
onramp.catalog(
options?: {
provider?: string // provider hint, e.g. "banxa"; defaults to the tenant's preferred provider
country?: string // explicit ISO-3166-1 alpha-2; wins over clientCountry / GeoIP
clientCountry?: string // softer hint → X-Client-Country header
fiat?: string // scope payment-method limits/fees + crypto pairs to this fiat
signal?: AbortSignal
},
): Promise<OnrampCatalogResponse>Example
const catalog = await stridge.onramp.catalog({ country: "US" })
console.log(catalog.suggested_fiat) // "USD" — preselect this
console.log(catalog.fiats.map((f) => f.code)) // ["USD", "EUR", …]
console.log(catalog.country.source) // "query" — how the country resolvedReturns
OnrampCatalogResponse — { provider, country, suggested_fiat, fiats[], crypto[], payment_methods[] }.
countryis anOnrampCountryResolutionDto({ code, source, confidence, warning? }) echoing how the country was resolved (source:"query" | "header" | "geoip" | "geoip_unavailable" | "tenant" | "none";confidence:"explicit" | "high" | "low" | "none") so the UI can warn on a low-confidence GeoIP miss.suggested_fiatis the code to preselect (also moved to the front offiats).fiats[]→{ code, name, symbol, min_fiat?, max_fiat? }(the limits are scoped per fiat when you passfiat);crypto[]→ one entry per(code, network_id)with{ code, name, network_id, network_name, network_symbol, decimals, eip155_id, logo, min_deposit_amount, contract_address?, is_native?, category?, unsupported_countries? };payment_methods[]→{ id, name, fiats[], cryptos?, min_fiat?, max_fiat?, fee_percent?, processing_time?, restricted_countries? }.
Scoped catalog reads
When you only need one slice — or need to re-scope after the user picks a fiat / crypto — call the focused reads instead of the full catalog. They share the provider / country / clientCountry options above.
const { fiats, suggested } = await stridge.onramp.fiats({ country: "US" })
const { crypto } = await stridge.onramp.crypto({ fiat: "USD" })
const { payment_methods } = await stridge.onramp.paymentMethods({ fiat: "USD", crypto: "USDC" })
const { countries } = await stridge.onramp.countries()| Method | Returns | Extra options |
|---|---|---|
onramp.fiats | OnrampFiatsResponse — { provider, country, suggested, fiats[] } | — |
onramp.crypto | OnrampCryptoResponse — { provider, country, crypto[] } | fiat? to constrain the pairs |
onramp.paymentMethods | OnrampPaymentMethodsResponse — { provider, country, payment_methods[] } | fiat?, crypto? to scope methods |
onramp.countries | OnrampCountriesResponse — { provider, countries[] } | provider? only |
onramp.quote
Prices a fiat amount into the destination crypto. Only the fiat side is sent — the crypto is derived server-side from the tenant's deposit preferences, so the quote always lands the asset your gateway settles.
Signature
onramp.quote(
payload: {
fiat_currency: string // e.g. "USD"
fiat_amount: string // positive decimal string, e.g. "100"
country?: string // resolved server-side when omitted
payment_method_id?: string // scope fees/limits to a method
},
options?: { clientCountry?: string; signal?: AbortSignal },
): Promise<OnrampQuoteResponse>Example
const quote = await stridge.onramp.quote({ fiat_currency: "USD", fiat_amount: "100" })
console.log(quote.asset_name, quote.asset_amount) // "USDC", "99.7"
console.log(quote.rate) // "1.003" — fiat per 1 unit of crypto
console.log(quote.fees.total) // total fee on top of the crypto receivedReturns
OnrampQuoteResponse — { provider, fiat_currency, fiat_amount, asset_name, asset_amount, rate, fees }. fees is { provider?, network?, total } (the provider / network legs are omitted when zero; total is always present). quote_id and expires_at are present only for providers that return them — treat them as optional. A provider-specific extra bag may also be present.
Status codes
200— quote priced.422— the amount is outside the provider's accepted range, or no route could be priced. ThrowsBackendErrorcarrying the provider's own validation message.
onramp.createSession
Binds a quote, allocates the deposit address, and opens a provider checkout order — returning the hosted-checkout URL you redirect the user to. The created session always starts at SESSION_PENDING.
Signature
onramp.createSession(
payload: {
owner: string // your end-user id; owner-scoped keys may only create under their own owner
fiat_currency: string // e.g. "USD"
fiat_amount: string // positive decimal string
destination_asset_symbol: string // asset the user receives, e.g. "USDC"
destination_network_id: string // network id from catalog crypto[].network_id
destination_address: string // the user's wallet; must be allowed for the tenant, else 403
country?: string
payment_method_id?: string
return_app_url?: string // your page URL; the checkout tab is bounced back here for resume
},
options?: {
clientCountry?: string
idempotencyKey?: string // Idempotency-Key header — a retried create returns the same session
signal?: AbortSignal
},
): Promise<OnrampSessionResponse>return_app_url (added when the new-tab / resume flow shipped) is your own page URL. The backend stores it only when its host is on the gateway key's referrer whitelist, then bounces the provider's checkout tab back to it with ?stridge_onramp_session={id} so a host UI can re-open and resume the session. It is not the provider redirect — the backend builds the env-pinned Stridge return page itself, so there is no redirect_url field to send.
Example
const session = await stridge.onramp.createSession(
{
owner: userId,
fiat_currency: "USD",
fiat_amount: "100",
destination_asset_symbol: "USDC",
destination_network_id: "60",
destination_address: walletAddress,
return_app_url: window.location.href,
},
{ idempotencyKey: `onramp-${userId}-${Date.now()}` },
)
window.open(session.checkout_url, "_blank") // hand off to the hosted checkout in a new tabReturns
OnrampSessionResponse (see The session shape). The checkout_url is present on create; the etag is the validator for cheap conditional polling.
Status codes
201— session created;stateisSESSION_PENDING.403— the destination address is not allowed/owned for this tenant (destination control).409— an active session already exists for the deposit address, or slippage exceeded during binding.422— no provider or deposit asset matched the route.
onramp.getSession
Reads a session by id. The canonical poll while the checkout is open.
Signature
onramp.getSession(
sessionId: string,
options?: { ifNoneMatch?: string; signal?: AbortSignal },
): Promise<OnrampSessionResponse>Example — poll until terminal
import { BackendError, isOnrampTerminalState } from "@stridge/sdk"
async function pollUntilTerminal(sessionId: string, etag?: string) {
for (;;) {
try {
const session = await stridge.onramp.getSession(sessionId, { ifNoneMatch: etag })
etag = session.etag
if (isOnrampTerminalState(session.state)) return session
} catch (err) {
// A matching ETag surfaces as BackendError(304) — nothing changed, keep polling.
if (!(err instanceof BackendError && err.statusCode === 304)) throw err
}
await new Promise((r) => setTimeout(r, 2_000))
}
}Reads honor If-None-Match for cheap ETag polling: pass the etag from the previous response and a matching read returns 304 Not Modified, which surfaces as a BackendError with statusCode === 304. Treat it as a "nothing changed" branch in your loop. Recommended cadence is one call every 2 seconds while a session is pending; stop once isOnrampTerminalState(state) is true.
onramp.listOwnerSessions
Owner-scoped, filterable, paginated session history — the read for a dashboard or an account "purchases" tab. Read-through cached and invalidated on every write.
Signature
onramp.listOwnerSessions(
owner: string,
options?: {
state?: string | string[] // filter by state; an array joins to a comma list
provider?: string
destinationAsset?: string // → destination_asset_symbol query
destinationNetwork?: string // → destination_network_id query
fiat?: string
country?: string
q?: string // free-text search
from?: string // created-at lower bound (RFC3339)
to?: string // created-at upper bound (RFC3339)
sort?: string
order?: "asc" | "desc"
limit?: number
offset?: number
includeTotals?: boolean // include aggregate totals in the response
signal?: AbortSignal
},
): Promise<OnrampSessionListResponse>Example
const { sessions, page_info } = await stridge.onramp.listOwnerSessions(userId, {
state: ["SESSION_COMPLETED", "SESSION_FAILED"],
limit: 20,
})Returns
OnrampSessionListResponse — { sessions[], page_info, totals? }. page_info is { limit, offset, count }. Each row is the flat OnrampSessionListItemDto (a richer shape than the create/get response): { session_id, state, provider, owner, fiat_currency, fiat_amount, destination_asset_symbol, destination_network_id, destination_address, deposit_asset, deposit_network_id, uda_address, provider_order_id, provider_status, deposit_tx_id, failure_code, created_at, updated_at, completed_at?, expires_at, version }. The list rows are the one place the provider's raw provider_status is surfaced. totals ({ total, by_state, fiat_volume }) is present only when includeTotals: true.
The session shape
createSession and getSession return OnrampSessionResponse, whose flow field summarizes the three legs of the purchase:
interface OnrampSessionResponse {
session_id: string
state: OnrampSessionState // create always returns "SESSION_PENDING"
checkout_url?: string // provider-hosted page; present on create
provider_order_id?: string
external_order_id: string
flow: {
fiat: { currency: string; amount: string } // what the user pays
entry: { // crypto the provider deposits, + the address
address: string
network_id: string
asset: string
amount: string
tx_hash?: string // the UDA deposit tx (settlement correlation)
settlement_state?: "pending" | "succeeded" | "failed" // the delivery-leg outcome
}
destination: { // what the user receives, and where
address: string
network_id: string
asset: string
amount: string
tx_hash?: string // receipt, populated alongside settlement_state: "succeeded"
}
}
expires_at?: string
etag: string // pass back as If-None-Match for cheap polling
}flow.entry is the Stridge Universal Deposit Address the provider settles crypto to; the gateway then swaps/bridges it to flow.destination — so a card buyer ends up with the same asset, on the same network, as an on-chain depositor.
SESSION_COMPLETED means the provider delivered the purchased crypto to the deposit address (flow.entry) — it does not mean the funds reached flow.destination yet. The destination delivery is the second on-chain leg, reported by flow.entry.settlement_state (pending → succeeded / failed), with flow.destination.tx_hash as the receipt once it succeeds. This makes a session a self-sufficient tracking resource: a consumer holding only the session id can reach the true terminal verdict from settlement_state alone. The field is forward-looking — when a backend doesn't expose it yet, treat its absence as unknown, not pending.
Session state & terminal helpers
The SDK ships the state enum, the terminal-state set, and a guard so you never hard-code state strings:
import { OnrampSessionState, ONRAMP_TERMINAL_STATES, isOnrampTerminalState } from "@stridge/sdk"| State | Terminal | Meaning |
|---|---|---|
SESSION_PENDING | no | Created; checkout open, waiting for payment + deposit. The only non-terminal state a client sees. |
SESSION_COMPLETED | yes | The provider delivered the purchased crypto to the deposit address. Track delivery to the destination via flow.entry.settlement_state. |
SESSION_EXPIRED | yes | The session/checkout window elapsed before completion. |
SESSION_FAILED | yes | Payment declined, cancelled, refunded, or an internal failure — see failure_code. |
OnrampSessionState— the enum (Pending/Completed/Expired/Failed→ the strings above). The transientCREATED/QUOTING/QUOTEDstates exist only in memory during the synchronous create call and are never observed by a client.ONRAMP_TERMINAL_STATES— aReadonlySetof the three terminal strings.isOnrampTerminalState(state)—truewhen polling should stop.
When state is SESSION_FAILED, the failure carries an OnrampFailureCode (also exported) — payment_declined, cancelled, refunded, deposit_timeout, provider_expired, quote_failed, slippage_check_failed, no_provider_for_route, no_deposit_asset_match, destination_not_allowed, concurrent_session_exists, uda_resolve_failed, provider_order_failed, create_session_crashed. Treat it as a loose string union — the backend may add codes ahead of the SDK.
setSessionState
A non-production override that drives a session straight to a terminal state without completing the real provider checkout — for deterministic integration tests against a third-party hosted page that is awkward to automate.
Signature
onramp.setSessionState(
sessionId: string,
payload: { state: OnrampSessionState; failure_code?: string },
options?: { signal?: AbortSignal },
): Promise<OnrampSessionResponse>Example
// Drive a pending session to the provider-completed state…
await stridge.onramp.setSessionState(session.session_id, { state: "SESSION_COMPLETED" })
// …or to a specific failure:
await stridge.onramp.setSessionState(session.session_id, {
state: "SESSION_FAILED",
failure_code: "payment_declined",
})This route lives under the /debug prefix and is not registered in production — it returns 404 there, so it can never short-circuit a real purchase. It uses the same gateway-key auth as every other onramp call. Point the SDK at env: "dev" to use it.
Cancellation
Every method accepts options.signal. Aborting it cancels the in-flight fetch; the SDK wraps the abort error in a BackendError. Use this for long-lived polling loops or per-request timeouts.
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 10_000)
try {
const session = await stridge.onramp.getSession(sessionId, { signal: controller.signal })
// …
} finally {
clearTimeout(timer)
}Related
- Cash (onramp) — the Gateway Kit rail that drives this surface end to end.
- API client — how
projectKeyflows into the authenticatedprojectHttpClient. - Gateway endpoints — the UDA lifecycle + pricing surface the onramp settles through.
- Errors & retries —
BackendErrorshape, the304conditional-read branch, retry policy for polling loops.