Skip to main content
The Ramp API buys and sells crypto against fiat through a ramp provider. One service covers both directions: direction is carried by the buy or sell order you send, not by the route, so the two cannot disagree. Assets are Solana only.

Auth

Use an API key:

Routes

Configuration fields

The options, quotes, and order-creation routes require both fields below. On the options route these are query parameters; on the quote and order routes they are body fields. Order reads and transfer actions identify the stored order in the route path and require neither field. The provider environment is not a Solana cluster. A sandbox order still settles on mainnet.

Amounts

Amounts are unsigned integers in the smallest unit the thing has, encoded as decimal strings: An amount finer than the currency’s minor unit is refused, never rounded. Read AssetOption.decimals and FiatCurrencyOption.exponent from the options route to build a valid value. A crypto asset is one of two shapes. Native SOL carries no mint:
exchangeRate is the one decimal string that is not an integer: a rate is a ratio and has no minor unit to scale to. It is display-only.

Get options

GET /wallet/api/ramp/options Query parameters: Response fields: decimals is Swig’s own value for the asset, not the provider’s.

Get quotes

POST /wallet/api/ramp/quotes
Send sell instead of buy for the other direction:
The response carries quotes, each with a route of provider and paymentMethod, plus a buy or sell arm holding the priced amounts, the totalFee, and the exchangeRate.
Quotes carry no identifier and must never be cached. The provider mints none and documents that they must not be reused, so the route you choose is re-priced when the order is created.

Create an order

POST /wallet/api/ramp/orders
requestId is your idempotency key and is unique within the configuration. Repeating it returns the stored order; repeating it with different inputs is refused with ALREADY_EXISTS. Mint it before the first attempt and reuse it across retries. The response is { "order": ... }. An order carries id, status, createdAt, updatedAt, and a buy or sell detail arm.

Read an order

GET /wallet/api/ramp/orders/{order_id} Returns { "order": ... }. A read of a non-final order refetches the provider’s record first, so polling this route is what advances the order and fills in the deposit address a sell needs. A buy carries the quote and an optional launchUrl. A sell also carries deposit once the provider assigns one, and transfer once one is prepared. A transfer.state is TRANSFER_STATE_UNSPECIFIED, PREPARED, SUBMITTED, LANDED, FAILED, or EXPIRED. A transfer settles only at confirmed or better, so a processed signature still reads as in-flight, and a read of the order does not advance it — only submission resolves an attempt.
A launchUrl is a user-specific session URL. Hand it to the customer who owns the order and keep it out of logs and analytics.

Prepare a transfer

POST /wallet/api/ramp/orders/{order_id}/transfer/prepare Sell only, and refused with FAILED_PRECONDITION unless every condition holds: the order is unfinished, it is a sell (only a sell needs a transfer), its network is mainnet (ramp transfers settle on mainnet only), it carries a deposit (the deposit address is not ready), and no other attempt is live (a transfer for this order is already in progress). The provider assigns the deposit while the customer is in the order’s checkout, so poll the order until it reaches AWAITING_TRANSFER and carries one.
The response is { "preparedTransfer": ... }, carrying the transfer, the preparedTransaction to sign, and the canonical deposit. The transfer is built from the provider’s own record rather than the quote, so a repriced amount funds the deposit correctly. transfer.expiresAt is the prepared blockhash’s lifetime.

Submit a transfer

POST /wallet/api/ramp/orders/{order_id}/transfer/submit
A successful response is { "transfer": ... } with a state of TRANSFER_STATE_LANDED and the solanaSignature that landed. The route waits for confirmed or better, so it does not return a pending transfer: every other outcome is an error, and the error is what tells you which attempt you still own. The prepared transaction is handed over exactly once. If you broadcast it and then lose it, submit again with an empty signedTransaction to resolve the attempt that is already live; resolution is answered from the stored attempt, so it does not need the bytes you lost. Retain the order and transfer ids until an attempt reaches a settled state. Reading the order reconciles it against the provider but does not resolve the transfer’s chain state, so an attempt that was submitted and never resolved stays SUBMITTED and keeps the order’s one live transfer slot. Prepare a replacement only after the previous attempt has been reported as failed — never in response to a confirmation timeout.

Errors

Validation failures return INVALID_ARGUMENT. A configuration that is disabled or not approved returns FAILED_PRECONDITION, and that stops orders already in flight, not only new ones. See Errors.

SDK equivalent

The Developer SDK wraps these routes as swig.ramp in TypeScript and Python.