Developers

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.

Note

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.

MethodHTTPReturns
onramp.catalogGET /gateway/onramp/catalogOnrampCatalogResponse
onramp.fiatsGET /gateway/onramp/fiatsOnrampFiatsResponse
onramp.cryptoGET /gateway/onramp/cryptoOnrampCryptoResponse
onramp.countriesGET /gateway/onramp/countriesOnrampCountriesResponse
onramp.paymentMethodsGET /gateway/onramp/payment-methodsOnrampPaymentMethodsResponse
onramp.quotePOST /gateway/onramp/quoteOnrampQuoteResponse
onramp.createSessionPOST /gateway/onramp/sessionsOnrampSessionResponse
onramp.getSessionGET /gateway/onramp/sessions/{id}OnrampSessionResponse
onramp.listOwnerSessionsGET /gateway/onramp/owners/{owner}/sessionsOnrampSessionListResponse
onramp.setSessionStatePUT /debug/gateway/onramp/sessions/{id}/stateOnrampSessionResponse

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 resolved

Returns

OnrampCatalogResponse{ provider, country, suggested_fiat, fiats[], crypto[], payment_methods[] }.

  • country is an OnrampCountryResolutionDto ({ 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_fiat is the code to preselect (also moved to the front of fiats).
  • fiats[]{ code, name, symbol, min_fiat?, max_fiat? } (the limits are scoped per fiat when you pass fiat); 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()
MethodReturnsExtra options
onramp.fiatsOnrampFiatsResponse{ provider, country, suggested, fiats[] }
onramp.cryptoOnrampCryptoResponse{ provider, country, crypto[] }fiat? to constrain the pairs
onramp.paymentMethodsOnrampPaymentMethodsResponse{ provider, country, payment_methods[] }fiat?, crypto? to scope methods
onramp.countriesOnrampCountriesResponse{ 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 received

Returns

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. Throws BackendError carrying 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>
Note

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 tab

Returns

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; state is SESSION_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))
  }
}
Note

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.

Two legs, two milestones

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 (pendingsucceeded / 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"
StateTerminalMeaning
SESSION_PENDINGnoCreated; checkout open, waiting for payment + deposit. The only non-terminal state a client sees.
SESSION_COMPLETEDyesThe provider delivered the purchased crypto to the deposit address. Track delivery to the destination via flow.entry.settlement_state.
SESSION_EXPIREDyesThe session/checkout window elapsed before completion.
SESSION_FAILEDyesPayment declined, cancelled, refunded, or an internal failure — see failure_code.
  • OnrampSessionState — the enum (Pending / Completed / Expired / Failed → the strings above). The transient CREATED / QUOTING / QUOTED states exist only in memory during the synchronous create call and are never observed by a client.
  • ONRAMP_TERMINAL_STATES — a ReadonlySet of the three terminal strings.
  • isOnrampTerminalState(state)true when 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",
})
Warning

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)
}
  • Cash (onramp) — the Gateway Kit rail that drives this surface end to end.
  • API client — how projectKey flows into the authenticated projectHttpClient.
  • Gateway endpoints — the UDA lifecycle + pricing surface the onramp settles through.
  • Errors & retriesBackendError shape, the 304 conditional-read branch, retry policy for polling loops.
Was this page helpful?