Off Ramp
Off-ramp operations (crypto to fiat) — sell crypto and receive fiat
Get indicative off-ramp pricing (crypto to fiat) without creating a quote. Pricing can be driven from either side: provide crypto_amount to compute the fiat payout, or fiat_amount to compute the crypto required (exactly one of the two). This endpoint is lightweight and does not reserve liquidity.
Use this when you need quick pricing discovery before calling /partner/offramp/quote. Optional filters: payment_method_slug, payment_network_slug, provider_scope, country_code. This endpoint is indicative and does not apply user/payment-details ownership checks; for executable pricing use quote (which still requires an API key).
Authentication: None. This is a public endpoint — no API key required (same as /partner/liquidity).
Note: EUR, AUD, and GBP off-ramp payouts require the user's physical address. Ensure the user's KYC includes address, city, and postal_code before initiating. See PATCH /partner/users/{user_uuid}/kyc to update.
Estimate retrieved successfully
Indicates if the request was successful
trueBad request — invalid parameters, missing required fields, unsupported cryptocurrency, or invalid provider_scope.
No offers available for the requested pair/amount
Internal Server Error
POST /api/v1/partner/offramp/estimate HTTP/1.1
Host: api-staging.unigox.com
Content-Type: application/json
Accept: */*
Content-Length: 205
{
"crypto_currency": "USDT",
"fiat_currency": "NGN",
"crypto_amount": "100.00",
"payment_method_slug": "access-bank",
"payment_network_slug": "nibss",
"country_code": "NG",
"provider_scope": "all",
"partner_fee_pct": "1"
}{
"success": true,
"data": {
"rate": "1530.12",
"crypto_amount": "99.024390",
"fiat_amount": "151519.20",
"fee_breakdown": {
"platform_fee": "1.463415",
"platform_fee_pct": 1.5,
"partner_fee": "0.975610",
"partner_fee_pct": 1,
"total_fee": "2.439025"
},
"payment_method_slug": "access-bank",
"payment_network_slug": "nibss",
"provider_scope": "all",
"is_indicative": true
}
}Get a price quote for an off-ramp operation (crypto to fiat). Returns the exchange rate, amounts, and a quote ID that can be used to initiate the order. Quotes expire after 60 seconds.
The user_uuid is the public UUID returned by POST /partner/users (the id field).
Choose exactly one payout ownership mode:
Self-payout: provide
payment_details_idfor an account owned byuser_uuid.Third-party payout: omit
payment_details_idand providesender_id,recipient_id,recipient_destination_id,sender_recipient_relationship, andpurpose_of_payment.
For a third-party payout, sender_id must identify the same real KYC subject as user_uuid. At quote time Unigox validates tenant ownership, sender eligibility, destination currency/rail, recipient screening, relationship, and purpose. It persists an immutable canonical recipient snapshot on the quote. Updating or archiving the recipient afterwards does not mutate the quote; request a new quote to use changed details.
On /initiate, Unigox revalidates the quote snapshot and creates a durable compliance case. In v1 there is no partner-visible review state machine: every control is enforced BEFORE the order exists, so a payout that fails one is rejected with an error code rather than returned as a held order. An order that was created was authorized (compliance.status: authorized, risk_decision: allow, requires_review: false). Operator review happens on the Unigox side over the same records; API-created third-party payouts use the same Compliance queue as portal-created payouts.
Anchor mode — provide exactly one of crypto_amount or fiat_amount:
crypto_amount
Crypto-anchored
Exact crypto cost; fiat payout varies by the rate at settlement
fiat_amount
Fiat-anchored
Exact fiat delivery to recipient; crypto cost may vary within a 1.20× safety bound
The response anchor_type field tells you which side is guaranteed: "crypto" or "fiat".
Fiat-anchored notes:
Useful for exact-payout use cases (e.g. deliver exactly 500,000 NGN).
If the crypto cost we quote exceeds 1.20× the expected amount, the order moves to
failedstatus and the deposit is automatically refunded from escrow back to the partner wallet. Create a new quote to retry.slippage_tolerance.type=amountin fiat-anchored mode is evaluated as crypto-side delta, not fiat-side (fiat is always exact by construction).Integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS):
fiat_amountmust be a whole number.
Fee structure: The fee_breakdown object shows all fee components transparently: platform_fee (our fee in crypto), partner_fee (your per-order markup in crypto — declare it by passing partner_fee_pct on the quote/estimate request, where 1 means 1%), partner_fee_pct (the percentage applied), and total_fee (sum of both). The partner fee is computed on the same crypto base as the platform fee (non-compounding) and is settled on-chain to your partner wallet on successful release. An order that does not settle is never charged the markup, and the refund returns the full funded amount with the markup in it. Note that the refund is not automatic once the escrow has been funded: the crypto stays there until you sign for it — see POST /orders/{order_id}/authorize-refund.
Apply your fee on the same side the platform does, or your receipts won't reconcile with our orders. crypto_amount in responses and webhooks stays partner-fee-exclusive, with two exceptions: a crypto-anchored onramp order (request declares crypto_amount) is sized up so the delivered amount stays exact after the markup is withheld, so crypto_amount there includes the markup; a crypto-anchored offramp order (request declares crypto_amount) carves both fees out of the pinned deposit, so crypto_amount and platform_fee both shrink relative to a no-markup request even though the markup itself stays out of crypto_amount. Every other case is unaffected — the markup shows only in fee_breakdown (and in the funding amount returned by transfer authorization for offramp — crypto_amount + fee_breakdown.partner_fee is what you actually fund). Fiat-anchored offramp orders have no cap on the markup — the deposit grows to cover it. Every other case hits a formula domain bound, not a business cap: onramp rejects partner_fee_pct >= 100 (the withheld or grossed-up fee would consume the entire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it when the fee would consume the entire pinned deposit.
Address requirement for EUR/AUD/GBP: EUR, AUD, and GBP payouts require the user's physical address (street, city, postal code). If the user's KYC does not include address data, the quote response will include a beneficiary_requirements field indicating which fields are needed. You can update the user's KYC with PATCH /partner/users/{user_uuid}/kyc to provide the address before or after initiating the order. If you initiate without address, the order will be created with status pending_address until the address is provided.
KYC requirement: The customer must be KYC-cleared before you can quote or transact for them. If the customer is not cleared, this endpoint returns 422 KYC_NOT_CLEARED and error.details.kyc_status carries their current partner-facing KYC status. The same check is re-applied as the order moves toward payout. A customer still in compliance review is reported as IN_PROGRESS — treat IN_PROGRESS as "not cleared yet" and keep polling GET /api/v1/partner/users/{user_uuid}/verification-status.
Partner API key for authentication. Required for all partner account endpoints.
Quote retrieved successfully
Indicates if the request was successful
trueBad request — invalid parameters or missing required fields
Unauthorized — invalid or missing API key
User or payment details not found
No offers available for the requested pair
The customer is not KYC-cleared. error.details.kyc_status carries the customer's current partner-facing KYC status — one of IN_PROGRESS, UNDER_REVIEW, PENDING_DOCUMENTS, VERIFICATION_REJECTED, or NOT_INITIATED. UNDER_REVIEW means a manual compliance review (up to 24h); keep the customer waiting and re-check. The message stays generic.
Internal server error
POST /api/v1/partner/offramp/quote HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 287
{
"user_uuid": "550e8400-e29b-41d4-a716-446655440000",
"payment_details_id": 987,
"crypto_currency": "USDT",
"crypto_amount": "100.00",
"fiat_currency": "NGN",
"provider_scope": "all",
"has_fiat_settlement_notification": false,
"partner_fee_pct": "1",
"slippage_tolerance": {
"type": "percent",
"value": 1.5
}
}{
"success": true,
"data": {
"quote_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"anchor_type": "crypto",
"rate": "1530.12",
"crypto_amount": "99.024390",
"fiat_amount": "151519.20",
"fee_breakdown": {
"platform_fee": "1.463415",
"platform_fee_pct": 1.5,
"partner_fee": "0.975610",
"partner_fee_pct": 1,
"total_fee": "2.439025"
},
"payment_details_id": 987,
"payment_method_id": 5,
"payment_network_id": 12,
"expires_at": "2026-02-01T12:00:30Z"
}
}Initiate an off-ramp order using a previously obtained quote. The quote must still be active (not expired or already used). The system refreshes the price and checks slippage tolerance before creating the order.
If the price has moved beyond the slippage tolerance, a PRICE_CHANGED_REQUOTE error is returned with details about the old and new rates, so the partner can request a new quote.
On success, returns the created order with status awaiting_liquidity_provider.
Address requirement for EUR/AUD/GBP: For EUR/AUD/GBP orders, if the user's KYC does not include a physical address, the order will be created with status pending_address. The response will include required_fields: ["address", "city", "postal_code"] and next_action: "provide_address". Call PATCH /partner/users/{user_uuid}/kyc to add the address, and the order will automatically advance.
Recommended flow for EUR/AUD/GBP:
Submit KYC with address/city/postal_code (
POST /kyc-submissions)Create payment details (
POST /payment-details) — address auto-populated from KYCGet quote (
POST /offramp/quote)Initiate (
POST /offramp/initiate) — proceeds without delays
KYC requirement: The customer must be KYC-cleared, exactly as described on POST /offramp/quote. If they are not, this endpoint returns 422 KYC_NOT_CLEARED with the customer's partner-facing status in error.details.kyc_status. The check is re-applied as the order moves toward payout.
Partner API key for authentication. Required for all partner account endpoints.
Quote ID from the quote endpoint. Payment details are taken from the quote.
a1b2c3d4-e5f6-7890-abcd-ef1234567890Optional partner-supplied external reference ID (e.g. your internal order ID). Stored on the order and echoed back in responses.
your-internal-order-idOff-ramp order created successfully
Indicates if the request was successful
trueBad request — missing required fields
Unauthorized — invalid or missing API key
Quote not found
Quote expired, already used, slippage exceeded, or — for a third-party payout — the payout agent has not confirmed recipient-context support, in which case no order is created and the quote is left untouched.
The request is well-formed but the party is not payable yet. No order is created and the quote is reverted, so the same customer can retry once the cause is fixed.
KYC_NOT_CLEARED — the customer is not KYC-cleared. error.details.kyc_status carries their current partner-facing status: IN_PROGRESS, UNDER_REVIEW, PENDING_DOCUMENTS, VERIFICATION_REJECTED, or NOT_INITIATED. UNDER_REVIEW means a manual compliance review (up to 24h); keep the customer waiting and re-check. The message stays generic.
THIRD_PARTY_CONTEXT_INVALID — the frozen third-party recipient context is no longer usable (archived recipient, screening no longer cleared, or a destination that stopped resolving).
SENDER_IDENTITY_REQUIRED — the corridor settles consumer-to-consumer (Chinese mobile wallets: the remitter shown on the receiving wallet has to be the paying customer, not Unigox) and their record cannot name them. error.details.missing_fields is what the rail needs; error.details.kyc_fields is the subset you can supply yourself, as the request body of PATCH /partner/users/{user_uuid}/kyc. When kyc_fields is shorter than missing_fields, the remainder (the name on the document, the country of residence) can only come from verification itself — the customer has to complete or redo KYC. Send the customer's own details: substituting anyone else's is what the rail's identity check exists to catch.
A field is also listed when the record holds a value the rail would refuse for its length: the rail rejects the whole payment, at payout, after the customer has funded, so it is refused here while a retry is still free. error.details.field_limits is the vendor's ceiling on any listed field that has one, keyed the way you write it in the KYC patch and counted in characters, not bytes; it is absent when no listed field has one. Read the ceilings from there rather than hard-coding them — they differ per corridor.
Internal server error
POST /api/v1/partner/offramp/initiate HTTP/1.1
Host: api-staging.unigox.com
X-API-Key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
Content-Length: 51
{
"quote_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}{
"success": true,
"data": {
"order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "awaiting_liquidity_provider",
"quote_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amounts": {
"crypto_amount": "99.024390",
"fiat_amount": "151519.20",
"crypto_currency": "USDT",
"fiat_currency": "NGN",
"rate": "1530.12"
},
"fee_breakdown": {
"platform_fee": "1.463415",
"platform_fee_pct": 1.5,
"partner_fee": "0.975610",
"partner_fee_pct": 1,
"total_fee": "2.439025"
},
"timeline": [
{
"status": "awaiting_liquidity_provider",
"timestamp": "2026-02-01T12:00:05Z",
"description": "Order created and awaiting liquidity provider"
}
],
"created_at": "2026-02-01T12:00:05Z"
}
}Last updated
